RecipientEditTextView.java revision 8659fc7fa4a245c962f8bc29b351acc56d02dfaa
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/* 22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project 32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License. 62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at 72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software 112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and 142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License. 152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips; 182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog; 20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData; 21b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 23b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 34156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 37572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 45c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 53a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 54b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 56c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 61572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 62416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 65cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 66b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 68156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 70e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 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, 89e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 115c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 117054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 118a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 125045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 140c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 141e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 142dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 151bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 162b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 163a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 169e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 1962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 1972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 20077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 202e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 204e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2050436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 210368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 211e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 215368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 216b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 221c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 228156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 229156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 230156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 231156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 232156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 235b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 237cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 238ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 239ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 240ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 241ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 242ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 243ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 25083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 25183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 25283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 25383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 258d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 259d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 260d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 261d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 264c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 265c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 266c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 267c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 268c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 270001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 271001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 272001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 273001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 2764f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 27705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 280c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 281a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 282c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 284a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 285a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 286a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 287a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 2886ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 291d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 292d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 293b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 294d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 2954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 2964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 2974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 298416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 2994fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 302b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 303b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 304b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 305b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 306b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3085a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3095a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 315a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 325001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (whatEnd != getSelectionEnd()) { 326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 327001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 328001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 329001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 330001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 331090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 332001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 34277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 34377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 34477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 34577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 34677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 34777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 351e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 352c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 353c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 354c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 355c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 356c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 359e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 363e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 38077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 38197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 38297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 38397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 385f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 386f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 387f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 388f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 389f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 390f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 3911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 3921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 3931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 3941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 3951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 3961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 398045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 399045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 400045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 401045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 402a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 403045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground : mInvalidChipBackground; 404045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 405045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 410e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4124221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4134221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4144221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4154221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4171a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4181a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 426045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 427045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 428045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 429045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4319024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 43490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 43590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 43690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 43790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 43890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 43990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 44090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 44190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 44290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4459024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 4519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 4529024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 453f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 454f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackground.getPadding(backgroundPadding); 455f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 456f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 457f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 458f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 459f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 4609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 465e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 46697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 467379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 46897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 47597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 47697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 47797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 47897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 47997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 48097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 48197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 4831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 4841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 4851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 4861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 489c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 4901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 4911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 49277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 4931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 4951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 496e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 499045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 50577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 50877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 513045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 514045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5155519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5165519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 518c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5195519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 520416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 523f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 524f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 534c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5354f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5364f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5374f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5394f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5421426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5441426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 546b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 547b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 54943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 550045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 551d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 552b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 553b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 554b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 555b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 556b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 557b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 558b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 559b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 560b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 561b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 562b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 563b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 564b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 565b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 566bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 567bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 568bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 569bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 570bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 571bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 572bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 573bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 574bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 575bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 576bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 578c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 579c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 5807bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (width != 0 && height != 0 && mPendingChipsCount > 0) { 5817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira postHandlePendingChips(); 5827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 58377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 584416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 585416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 586416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 587416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 588416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 589416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 590416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 591416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 592416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 593416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 594c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 595c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 6010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void handlePendingChips() { 602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount <= 0) { 603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira return; 604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 6057bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6067bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6077bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6087bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6097bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6107bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount); 613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 6270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 6290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mTemporaryRecipients != null 632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 634a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 636a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 637a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 638a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 639a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 640a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 64177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 642a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 643a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 644a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 645a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 646a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to 647a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // showing addresses for all of them. 648a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 64977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 65077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 651a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 652a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 6760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 6770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 6780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 6800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 6860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 6870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 6891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 6901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 6911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 6921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 6931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 694b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 6951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 6960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 6970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 6980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 700d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 701d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira String destText = entry.getDestination(); 702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira destText = (String) mTokenizer.terminateToken(destText); 703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int textLength = destText.length() - 1; 705d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 706d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 707d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 708d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 709d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 710d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 712d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 713d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 714d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 716d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 717d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 718d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 7196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 720d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 721d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 72277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 7230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 726d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 727d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 728d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 7301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 7316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 732454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 733454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 7341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 7351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 7361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 7376ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 7386ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 7396ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 7406ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 7416ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 7421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 7456ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 746a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 7476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 7486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 749a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 750a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 751a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 752a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 753a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 754a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 755a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 756a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 757a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 758a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 759a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 760a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 761a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 762a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 763490556a764a879cd0eaff358e90705cc1335c92eErik } 764d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7656ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 766454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 767a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 768a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 7691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 7716ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 7726ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 7736ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 7746ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 7751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 7761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 7771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 7781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 783c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 784c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 785c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 787c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 788c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 7908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 7918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 7928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 7938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 7948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 7958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 7978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 7988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 800c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 802c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 804c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 80995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 81095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 81195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 81295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 81395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 81495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 81595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 8188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 8198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 8208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 8218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 8228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 8238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 82495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 825c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 827c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 830d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 833e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 834e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 835e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 836e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 837e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 838e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 84095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 841e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 842e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 843e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 844e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 845e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 846e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 847e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 848e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 849e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 850e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 851e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 852c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 855c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 856e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 857e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 858e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 859e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 860e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 861e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 862e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 863e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 864e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 865045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 866045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 867045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 868045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 869045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 870045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 871045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 8734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 874045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 8754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 876dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 879e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 880e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 8844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 889e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 894e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 8964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 897054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 8994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 901001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 9111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 912d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 913d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 914d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 9155ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira if (chipText != null) { 9165ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 9175ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 918d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 919d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 9204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 921d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 922d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 9234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 924d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 925d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 9271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 9281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 9291e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 9301e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 9311e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 9321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 9331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 93405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 9351e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 93939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 94039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 94139f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 94239f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 94339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 946e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 9493b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 9503b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 9513b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 9523b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 9533b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 9543b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 955054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 95605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 95705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 9588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 9608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 9618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 964b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 965b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 966b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 967b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 968b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 9762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 9772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 979ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 982b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 983b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 984b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 985b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 986b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 987b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 988b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 989b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 991c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 992c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 993c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 995c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1015b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 101836d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 10258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 10268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 10278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 10288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1031d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1032d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1033d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1034d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1036d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 10386caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1039b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1040b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 10410436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1042c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1043c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1045c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1047c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1048c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1049c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1051c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 10528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 10538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1055c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1059c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1060416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 10615753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 10625753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 10635753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1064c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1066c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1068c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1072416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1073416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1074c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1075416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1076416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1077416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1078b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1079b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1081c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1082b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1085e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 108677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1087416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1088b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1089368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1090e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1091e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1097b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1098b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1099b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1100b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1101b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1102b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1103b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1104b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1105e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1106e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1107e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1108e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1109e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1110e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 11161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 11171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 11181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 11191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 11201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1121a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1126b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1127e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1128b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1129b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 11484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 11494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1153c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 11614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 11624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 11634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 11654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1168c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1172b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1173b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1174b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11818659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira private String createDisplayText(RecipientEntry entry) { 11828659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 11838659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 11848659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 11858659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 11868659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 11878659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 11888659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = token.toString(); 11898659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String trimmedDisplayText = displayText.trim(); 11908659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 11918659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 11928659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira .terminateToken(displayText) : displayText; 11938659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 11948659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1195fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 11968659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = createDisplayText(entry); 1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1198b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int textLength = displayText.length()-1; 1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 12036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 12046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 12066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 12168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 12178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 12188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1224c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 12250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 12260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 12271e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 12281e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 12291e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1231c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1233c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1234c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1235c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1236c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 12374221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 12384221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 12394221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 12404221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 12440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 12450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 12460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 12480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 12490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 12500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 12510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1252c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1253c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 12540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 12550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 12560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 12570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 12580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 12600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1264c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 12657a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 12667a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 12677a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 12687a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 12697a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1270c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 127483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 127583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 127683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 127783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 12786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private RecipientChip[] getSortedRecipients() { 12796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 12806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 12816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 12826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 12836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 12846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 12856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 12866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 12876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 12886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 12896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 12906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 12916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 12926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 12936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 12946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 12976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 12986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 130383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 13047a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 13057a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 13067a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 13077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 13154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 13204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 13244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 13298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 13324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1336045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1337045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1338045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 13398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createMoreChip() { 1341bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1342bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1343bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1344bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 13456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 13466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 13476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 13486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 13496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 135083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 13510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 13534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 135583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 13564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1357c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1358c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1359c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1360c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1361c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1362c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 13634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0), 1367c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint); 13684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 13704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 13716f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1373368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 13750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 13760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 13784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1379368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 13804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 13814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 13826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1383368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1384368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 13859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1386368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 13879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1388368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1389368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 13909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 13916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 13926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 13936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 13946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 139577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1396368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 13974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 139877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 139977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 140077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 14014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 140277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 14030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 14044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 14088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 14098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 14104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 14114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 14124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 14134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 14144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 14154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 14164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 14174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 141864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira RecipientChip[] recipients = getRecipients(); 141964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 142064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 142164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 14224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 14234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 14246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 14250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 14260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 14276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 14286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 142964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 143064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 143164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 143264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 143364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 14346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // -1 for the space! 143564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1436bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1437bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1438bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1439bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1440bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 14414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 14434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1448b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1449b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1450b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1451b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1452b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1453b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1454b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1455b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1456b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1457b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1459b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 14611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 14672f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1469b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1470b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1471b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1472b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1473b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1474b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1475b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1477b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1478b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1479fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1480b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 148183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1482b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 148383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 14848b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 148583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1486b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 14871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 14881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1489c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 14901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 14916ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1492b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1493b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 14941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 14951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 14961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 14971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 14981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 14991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 15001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 15011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 15021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 15031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 15051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 15061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 15071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 15081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 15091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 15101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 15121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 15131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 15141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 15166ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 15171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1518fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1520fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 15221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 15231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 15241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 15251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 15261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 15271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1528e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 15291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 15301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 15311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 15321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 15331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 15341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 15351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 15361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 15371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 15381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 15401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 15411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 15421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 15431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 15441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1551b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1555c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1556b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 15575753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 15585753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 15595753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 15605753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1562c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 15648b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 15658b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 15668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 15678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 15688b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 15698b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 15708b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1577b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1579b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1580b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1584b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1589b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 16063656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1607b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1608b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1609b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1610b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void removeChip(RecipientChip chip) { 1611b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1613b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int toDelete = spanEnd; 1616b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1617b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1618b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1619b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1621b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Always remove trailing spaces when removing a chip. 16228b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira toDelete++; 1624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1625b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira text.delete(spanStart, toDelete); 1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1630b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1632b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1633b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1634b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1635b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1636b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1637b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1638b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1639b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1640c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1641b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1642b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1643b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1644b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira editable.insert(0, chipText); 1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1650b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // There may be a space to replace with this chip's new associated 1651b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // space. Check for it. 1652b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int toReplace = end; 1653b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1654b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira && editable.charAt(toReplace) == ' ') { 1655b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira toReplace++; 1656b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1657b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira editable.replace(start, toReplace, chipText); 1658c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1661b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1662c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1663b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1664c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1665b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1676c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1677c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1679c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1680368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1681368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1682368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1683368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1684311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1685311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1686311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1687311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1688311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1689311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1690e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1691e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1692e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 16931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 16941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 16951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 16961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 16971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 16981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 16991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 17001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 17011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 17021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 17041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 17051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 17071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1710368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1711e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1712e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 17135753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 17145753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 17155753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1716e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1717e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1718e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1719e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1720e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1721e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1722054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1723e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1724054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 172576ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1726054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1727054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1728054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1729054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1730054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1731054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1732e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1733e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1734e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1735e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1736e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1737e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1738e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1739e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1740e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 17413b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1742e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1743e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1744e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1745e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1746e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1747e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1748e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1749e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 17501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1751e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1752e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1753e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1754e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1755e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1756e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 17776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 17868659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 178777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 17931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 17961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 17971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 179877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 180177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 180277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 180377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 180677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 180777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 180877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 180977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 181077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 181177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 181277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 181377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 181477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 181577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 181677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1817b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1818b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1819b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 18206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 18216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1822b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 18236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 182477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 182577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 182677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 182777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 182877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 182977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 183077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 183177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 183377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 183577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 183877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 183977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 184077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 184177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 184277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 184377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 184477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 184577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 184677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 184777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 184877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 18498659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 185077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 185177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 185277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 185377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 18541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 185577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 185677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 18571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 18581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 185977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 186077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 186177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 186277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 186377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 186477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 186577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 186677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 186777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 186877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 186977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 187077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 187177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 18736f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 18746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 18756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 18766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 18776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 18786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 18796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 18806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 18816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1884b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1885b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 1886b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1887b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1888b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1889b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1890b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 1891b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1892b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1893b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1894b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1895b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1896b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 1897b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 1898b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 1899b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1900b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 1901b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 1902b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1903b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 1904b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 1905b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 1906b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 1907b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1908b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1909b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1910b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 1911b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 1912b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 1913b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 1914b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 1915b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 1916b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 1917b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 1918b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 1919b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1920b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1921b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1922b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 1923b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1924b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1925b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1926b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1927b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1928b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 1929b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1930b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1931b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1932b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1933b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 1934b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1935b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1936b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1937b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1938b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1939b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 1940b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 1941b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1942b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1943b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1944b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 1945b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 1946b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 1947b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 1948b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 1949b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 1950b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 195177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 1952