RecipientEditTextView.java revision e9278b0f8b883a987a913c5d785455f5d0e646a9
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; 6197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereiraimport android.view.ViewGroup; 62572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 63416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 66cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 71416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 72c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 73b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 74a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 7977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 87b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 88b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener { 90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 114c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 116054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 117a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 118e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 124045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 125045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 132c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 139c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 140e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 141dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 14777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 150bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 151bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 161b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 162a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 163a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 167416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 168e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 169e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 1952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 1962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 19777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 2011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 206368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 210368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 216c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 218156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 221156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 227156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 230b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 231ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 232cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 233ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 234ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 235ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 238ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 24583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 24683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 24783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 24883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2504fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 253d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 254d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 255d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 256d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 257c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 260c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 261c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 264c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 265001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 266001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 267001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 268001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 270c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 2714f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 27205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 276a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 277c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 279a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 280a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 281a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 282a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 2836ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 286d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 287d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 288b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 289d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 2904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 2914fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 2924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 293416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 2944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 297b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 298b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 299b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 300b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 301b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mSelectedChip != null) { 3044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 309a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (whatEnd != getSelectionEnd()) { 320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 325090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 33677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 33777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 33877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 33977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 34077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 34177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 345e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 346c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 347c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 348c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 349c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 350c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 353e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 357e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 3731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 37477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 37597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 37697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 37797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.setBounds(width - deleteWidth, 0, width, height); 3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 3831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 387045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 388045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 390045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 391045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 392a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 393045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground : mInvalidChipBackground; 394045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 395045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 396e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 400e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4024221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4034221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4044221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4054221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4071a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4081a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 416045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 417045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 418045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 419045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4219024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 42490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 42590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 42690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 42790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 42890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 42990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 43090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 43190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 43290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 4429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 443379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira RectF dst = new RectF(width - iconWidth, 0, width, height); 4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER); 4459024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 45197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 452379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 45397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 46097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 46197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 46297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 46397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 46497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 46597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 46697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 4681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 4691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 474c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 4751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 4761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 47777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 4781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 4801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 481e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 484045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 4851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 4891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 49077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 49377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 4952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 498045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 499045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5005519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5015519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 503c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5045519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 505416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 50697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 50797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 508f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 509f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5204f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5214f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5224f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5244f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5271426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5291426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 53443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 535045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 536d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 537b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 538b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 539b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 540b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 541b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 542b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 543b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 544b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 545b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 546b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 547b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 548b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 549b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 550b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 551bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 552bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 553bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 554bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 555bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 556bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 557bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 558bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 559bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 560bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 561bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 563c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 564c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 5657bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (width != 0 && height != 0 && mPendingChipsCount > 0) { 5667bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira postHandlePendingChips(); 5677bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 56877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 569416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 570416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 571416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 572416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 573416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 574416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 575416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 576416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 577416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 578416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 579c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 580c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 581a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 582a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 583a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 584a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 585a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 5860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void handlePendingChips() { 587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount <= 0) { 588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira return; 589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 5907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 5917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 5927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 5937bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 5947bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 5957bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount); 598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 6120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 6140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mTemporaryRecipients != null 617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 62677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to 632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // showing addresses for all of them. 633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 63477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 63577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 636a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 637a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 6741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 6751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 6761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 6771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 6781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 679b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 6801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 6810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 6820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 686d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira String destText = entry.getDestination(); 687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira destText = (String) mTokenizer.terminateToken(destText); 688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int textLength = destText.length() - 1; 690d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 691d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 695d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 698d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 699d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 701d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 7046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 705d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 706d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 70777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 712d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 713d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 7151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 7166ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 717454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 718454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 7191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 7201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 7211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 7226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 7236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 7246ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 7256ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 7266ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 7271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 7306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 7316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 7326ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 7336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira token = mValidator.fixText(token).toString(); 734490556a764a879cd0eaff358e90705cc1335c92eErik if (!TextUtils.isEmpty(token)) { 735490556a764a879cd0eaff358e90705cc1335c92eErik // protect against the case of a validator with a null domain, 736490556a764a879cd0eaff358e90705cc1335c92eErik // which doesn't add a domain to the token 737490556a764a879cd0eaff358e90705cc1335c92eErik Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token); 738490556a764a879cd0eaff358e90705cc1335c92eErik if (tokenized.length > 0) { 739490556a764a879cd0eaff358e90705cc1335c92eErik token = tokenized[0].getAddress(); 740490556a764a879cd0eaff358e90705cc1335c92eErik } 741d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7426ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 743454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructFakeEntry(token); 7451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 7476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 7486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 7496ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 7506ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 7511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 7521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 7531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 7541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 759c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 760c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 761c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 763c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 764c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 7668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 7678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 7688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 7698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 7708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 7718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 7758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 776c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 777c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 779c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 7838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 784c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 78595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 78695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 78795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 78895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 78995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 79095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 79195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 7928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 7948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 7958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 7968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 7978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 7988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 80095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 802c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 804c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 806d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 810e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 811e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 812e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 813e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 814e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 81695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 819e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 821e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 822e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 827e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 832e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 833e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 834e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 835e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 836e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 837e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 838e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 839e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 840e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 841045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 842045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 843045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 844045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 845045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 846045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 847045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 8488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 8494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 850045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 8514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 852dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 8604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 863e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 8724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 873054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 8754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 877001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 879e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 880e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 884e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 8871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 888d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 889d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 890d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 891d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(start, end, chipText); 892d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 893d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 8944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 895d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 896d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 8974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 898d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 899d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 9011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 9021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 9031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 9041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 9051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 9061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 9071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 90805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 9091e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira CharSequence chipText = createChip(entry, false); 921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira editable.replace(start, getSelectionEnd(), chipText); 922054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 92305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 92405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 9288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 931b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 932b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 934b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 9432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 9442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 946ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 949b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 950b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 951b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 952b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 953b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 954b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 955b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 956b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 982b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 984c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 98536d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 9908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 9918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 9928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 9938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 9948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 9958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 998d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 999d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1000d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1001d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1002d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1004d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 1006b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) { 1007b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1008b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1009b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mCopyDialog == null && action == MotionEvent.ACTION_UP) { 1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1018b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 10208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // Selection may have moved due to the tap event, 10218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // but make sure we correctly reset selection to the 10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // end so that any unfinished chips are committed. 10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1025b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1026c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1027b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1031416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1033c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1039c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1040416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1041416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1042c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1043416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1044416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1045416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1047b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1049c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1051b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 105377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1054416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1056368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1057e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1058e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1066b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1076b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 10771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 10781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 10791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 10801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 10811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1082a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1085b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1086b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1087b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1088e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1089b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1090b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 11094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 11104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1114c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11214fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 11224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 11234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 11244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11254fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 11264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11274fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1129c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1133b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1134b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1135b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1142fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 1143c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira String displayText = entry.getDestination(); 11444f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira displayText = (String) mTokenizer.terminateToken(displayText); 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1146b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int textLength = displayText.length()-1; 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 11516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 11526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 11546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 11658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 11730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 11740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 11751e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 11761e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 11771e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 11854221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 11864221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 11874221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 11884221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 11920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 11950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 11960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 11970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 11980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 11990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1200c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1201c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 12020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 12030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 12040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 12050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 12060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 12080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 12137a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 12147a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 12157a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 12167a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 12177a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 122283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 122383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 122483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 122583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 12266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private RecipientChip[] getSortedRecipients() { 12276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 12286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 12296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 12306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 12316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 12326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 12336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 12346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 12356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 12366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 12376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 12386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 12396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 12406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 12416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 12426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 12456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 12466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 125183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 12527a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 12537a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 12547a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 12557a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1256c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1257c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 12634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 12684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 12768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 12778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 12784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 12804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1284045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1285045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1286045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 12878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 12880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createMoreChip() { 1289bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1290bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1291bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1292bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 12936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 12946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 12956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 12966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 129883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 12990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 13014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 130383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 13044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1305c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1306c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1307c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1308c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1309c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1310c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 13114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 13124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 13144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0), 1315c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint); 13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 13196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 13204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1321368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 13224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 13230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 13240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 13264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1327368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 13284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 13294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 13306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1331368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1332368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 13339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1334368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 13359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1336368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1337368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 13389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 13396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 13406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 13416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 13426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 134377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1344368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 13454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 134677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 134777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 134877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 13494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 135077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 13510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 13524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 13568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 13578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 13594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 13604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 13614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 13624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 13634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 13674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 13686f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 13690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 13700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 13716f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 13726f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 13736f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipStart = editable.toString().indexOf(token); 13746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // -1 for the space! 13756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipEnd = Math.min(editable.length(), chipStart + token.length()); 1376bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1377bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1378bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1379bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1380bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 13814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 13834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1388b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1389b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1390b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1391b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1392b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1393b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1394b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1395b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1396b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1397b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 14001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 14011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 14021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 14031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 14041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 14051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 14061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 14072f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 14081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1409b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1410b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1411b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1413b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1414b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1415b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1416b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1417b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1418b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1419fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1420b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 142183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1422b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 142383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 14248b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 142583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1426b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1429c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 14316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1432b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1433b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 14341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 14351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1444b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 14481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 14491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 14501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 14566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1458fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1459b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1460fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 14701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 14711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 14721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 14731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 14741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 14751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 14761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 14771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 14791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 14801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 14811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 14821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 14831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1485b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1486b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1487b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1488b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1490b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1491b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1492b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1493b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1494c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1495b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 14962f5589283d93933751c20791ef42dc7eab87061aMindy Pereira Log.e(TAG, "The chip being unselected no longer exists."); 1497b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1498c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 15008b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 15018b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 15028b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 15038b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 15048b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 15058b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 15068b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1527c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 15433656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1545b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void removeChip(RecipientChip chip) { 1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1551b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int toDelete = spanEnd; 1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1556b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Always remove trailing spaces when removing a chip. 15598b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira toDelete++; 1561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira text.delete(spanStart, toDelete); 1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1567b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1578b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1579b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1580b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1584b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira editable.insert(0, chipText); 1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1587b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // There may be a space to replace with this chip's new associated 1588b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // space. Check for it. 1589b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int toReplace = end; 1590b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1591b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira && editable.charAt(toReplace) == ' ') { 1592b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira toReplace++; 1593b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1594b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira editable.replace(start, toReplace, chipText); 1595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1601c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1606b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1607b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1608b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1609b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1610b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1611b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1614c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1617368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1618368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1619368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1620368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1621311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1622311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1623311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1624311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1625311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1626311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1627e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1628e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1629e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 16301e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 16311e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 16321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 16331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 16341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 16351e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 16361e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 16371e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 16381e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 16391e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16401e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 16411e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 16421e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16431e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 16441e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 16461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1647368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1648e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1649e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1650e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mSelectedChip != null) { 1651e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1652e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1653e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1654e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1655e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1656e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1657054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1659054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 166076ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1661054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1662054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1663054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1664054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1665054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1666054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1675e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mValidator != null && mValidator.isValid(sub)) { 1677e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1678e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1679e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1680e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1681e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1682e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1683e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1684e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1686e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1687e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1688e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1689e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1690e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1691e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String displayText = entry.getDestination(); 16966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (displayText.indexOf(",") == -1) { 16976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira displayText = (String) mTokenizer.terminateToken(displayText); 16986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 170677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 17166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 172277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira addresses[i] = originalRecipients.get(i).getEntry().getDestination(); 172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 173477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 174677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 174777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 175077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1756b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1757b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1758b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 17596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 17606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1761b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 178677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 178777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira addresses[i] = originalRecipients.get(i).getEntry().getDestination(); 178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 17931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 17961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 17971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 179877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 179977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 180177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 180277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 180377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 180577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 180977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 181077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1811b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 18126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 18136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 18146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 18156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 18166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 18176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 18186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 18196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 18206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog = new Dialog(getContext()); 1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1877b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1878b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1879b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 1880b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 1881b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog = null; 1882b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1883b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1884b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1885b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 1886b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 1887b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 1888b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 1889b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 1890b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 1891b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 189277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 1893