RecipientEditTextView.java revision 53958d6d0ba2a0765fd3d272295b81c6d89a68d8
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/* 22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project 32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License. 62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at 72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software 112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and 142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License. 152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips; 182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog; 20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData; 21b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 23b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 34156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 38572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 46c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 52c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 54a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 55b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 57c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 62572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 63416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 66cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 71e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 72416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 73c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 75a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 88b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 116c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 118054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 119a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 141c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 142e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 143dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 197a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira private Runnable mDelayedShrink = new Runnable() { 198a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 199a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira @Override 200a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira public void run() { 201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira shrink(); 202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira }; 205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 2062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 2072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 20877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 20977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 21077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 212e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 214e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2150436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 216b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 220368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 221e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 225368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 231c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 233156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 234156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 235156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 236156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 238156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2436ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 245b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 246ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 247cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 248ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 249ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 251ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 252ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 253ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 26083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 26183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 26283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 26383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 266b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2674fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 268d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 27222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 281c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 282c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 286c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 287c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 288c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 289001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 290001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 291001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 292001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 2954f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 29605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 297c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 300a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 303a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 304a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 305a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 306a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3076ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 308c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 310d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 311d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 312b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 313d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 317416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 321b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 322b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 323b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 324b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 325b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3275a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3285a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 331a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 332a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 333a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 334a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 335a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 336a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 337a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 338a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 341001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 342001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 343001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 344a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 345001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 346001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 347001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 348001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 353a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3548a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 355a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 356a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) { 361001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 363001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 364001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 366090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 367001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 37777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 37877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 37977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 38077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 38177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 38277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 386e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 387c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 388c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 389c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 390c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 391c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 3951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 3961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 3971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 398e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 3991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 41577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 41697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 41797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 41897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 420f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 421f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 422f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 423f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 424f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 425f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 433045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 434045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 435045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 437a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira String destination = contact.getDestination(); 438a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira return (mValidator != null && !TextUtils.isEmpty(destination) && mValidator 439a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira .isValid(destination)) ? mChipBackground : mInvalidChipBackground; 440045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 442e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 446e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4484221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4494221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4504221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4514221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4531a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4541a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 462045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 463045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 464045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 465045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4679024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4699024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 47090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 489f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 490f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackground.getPadding(backgroundPadding); 491f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 492f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 493f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 494f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 495f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 4969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 501e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 50297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 503379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 50497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 51197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 51297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 51397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 51497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 51597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 51697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 51797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 5181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 525c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 52877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 532e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 535045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 537c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 54177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 543c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 54477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 549045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 550045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5515519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5525519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 554c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5555519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 556416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 55797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 55897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 559f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 560f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5714f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5724f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5734f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5754f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5781426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5801426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 582b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 583b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 58543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 586045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 587d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 588b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 589b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 590b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 592b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 593b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 594b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 595b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 596b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 602a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 603a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 604a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 605a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 606a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 607bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 608bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 609bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 610bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 611bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 612bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 613bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 614bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 615bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 616bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 617bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 619c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 620c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 62153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 62253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 62353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 62453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 62553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 62653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6277bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 62877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 629416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 630416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 631416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 632416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 633416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 634416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 635416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 636416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 637416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 638416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 640c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 641a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 642a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 643a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 644a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 645a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 64653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 64753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 64853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 64953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 65053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 65153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 65253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 65353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 65453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 65553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 65653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 65753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 658a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 65953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 66053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 66153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6627bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6637bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6647bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6657bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6667bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6677bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 673a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 674a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 675a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 676a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 678a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 679a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 680a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 681a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 682a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 683a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 684a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 685a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 686a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 687a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 6880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 689a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 6900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 691a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 69222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 70277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 70722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 70977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 71077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 7200fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 7210fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 7220fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 72522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 7290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 7300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7501e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7511e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7521e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 755b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 761d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 762a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira String destText = createDisplayText(entry); 763d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 764a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira int textLength = destText.length(); 765d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 766d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 767d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 768d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 769d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 770d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 771d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 772d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 773d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 774d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 775d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 776d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 777d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 77822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 77922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 78022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 7816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 782d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 78477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 7850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 7921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 7936ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 794454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 795454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 7961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 7971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 7981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 7996ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8006ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8016ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8026ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8036ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8066ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8076ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 808a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8096ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8106ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 811a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 812a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 813a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 814a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 815a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 816a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 817a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 818a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 819a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 820a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 821a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 822a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 823a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 824a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 825490556a764a879cd0eaff358e90705cc1335c92eErik } 826d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 828454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 829a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 830a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8366ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 845c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 846c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 847c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 848c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 850c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 862c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 864c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 87195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 87295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 87395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 87495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 87595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 87695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 87795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 88695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 887c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 888c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 889c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 891c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 892d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 895e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 896e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 897e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 898e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 899e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 900e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 901c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 90295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 903e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 904e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 905e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 906e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 907e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 908e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 909e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 910e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 911e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 912e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 913e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 918e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 919e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 921e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 922e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 927045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 928045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 929045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 930045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 931045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 932045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 933045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 936045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 938dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 941e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 942e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 949e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 950e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 951e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 954e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 955e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 956e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 957e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 959054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 963001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 970a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (editable.length() > tokenEnd && editable.charAt(tokenEnd) == ',') { 971a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira tokenEnd++; 972a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 973a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira String text = editable.toString().substring(start, tokenEnd); 974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 9761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 977d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 978d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 979d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 9805ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira if (chipText != null) { 9815ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 9825ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 983d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 984d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 985f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 9864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 987d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 988d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 9894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 990d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 991d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 9924031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 9934031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 994f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 9954031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 996f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 997f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 998f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 999f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1000f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1001f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1002f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1003f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1004f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1005f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1006c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1008c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1009c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1010c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1011c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1012f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1013f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1014f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1015a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1017f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1018f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1019f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1020f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1021e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10241e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10251e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10261e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 102905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10301e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1031e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1032e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1033e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 103439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 103539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 103639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 103739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 103839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1039e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1040e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1041e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1042e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1043e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10443b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10453b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10463b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10473b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 10483b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 10493b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1050054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 105105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 105205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1064c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1066c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1068c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 10712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 10722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 10734031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10744031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1075ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1078b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1079b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1081b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1082b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1085b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 111436d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1127d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1128d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1129d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1130d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1132d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11346caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1135b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1136b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11370436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 11488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 11498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1150b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1152b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1156416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 11575753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 11585753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 11595753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1170c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1174b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1175b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1176b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1177c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1178b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1179b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1180b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1181e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 118277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1183416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1184b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1185368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1186e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1187e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1188b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1189b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1190b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1191b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1192b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1193b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1194b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1196b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1197b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1198b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1201e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1202e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1203e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1204e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1205e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1206e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1217a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1223e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1231c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1233c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1234c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1235c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1236c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1237c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 12444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 12454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 12484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1249c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1253c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1254c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1255c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 12574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 12584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 12594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 12604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 12614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 12624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1264c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1265c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1266c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12774031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 12784031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ String createDisplayText(RecipientEntry entry) { 12798659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 12808659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 12818659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 12828659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 12838659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1284a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1285a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1286a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1287c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1288c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1289c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1290c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1291a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 12928659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 12938659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = token.toString(); 12948659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String trimmedDisplayText = displayText.trim(); 12958659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 12968659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 12978659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira .terminateToken(displayText) : displayText; 12988659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 12998659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1300fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 13018659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = createDisplayText(entry); 1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1303a1d52e9203ea3cdfc0d1c69a8a4f99445fdd5ca1Mindy Pereira int textLength = displayText.length(); 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 13228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 13238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 13300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 13310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 13321e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 13331e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 13341e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1339c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 13424221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 13434221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 13444221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 13454221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1346f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1347c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 13500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 13510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 13520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 13540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 13550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 13560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 13570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1358c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1359c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 13600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 13610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 13620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 13630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 13640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 13660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1370c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 13717a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 13727a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 13737a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 13747a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 13757a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 138083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 138183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 138283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 138383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 13844031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 13854031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ RecipientChip[] getSortedRecipients() { 13866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 13876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 13886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 13896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 13906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 13916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 13926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 13936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 13946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 13956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 13966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 13976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 13986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 13996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 14046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 14056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 141083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 14117a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14127a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14137a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 14147a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 14224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 14274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 14314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 14368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 14374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 14394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1442a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1443a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 144422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 144522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 144622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 144722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 144822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 14498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1450045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1451045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1452045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 14538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1454a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1455a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1456bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1457bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1458bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1459bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 14606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 14616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 14626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 14636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 146583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 14660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 14670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 14684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14696f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 147083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 14714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1472c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1473c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1474c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1475c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1476c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1477c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 14784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 14794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 14804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 148122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 148222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 148322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 148422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 148522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 148622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 14874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 14894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 14914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1492368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 14934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 14940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 14950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 14960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 14974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1498368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 14994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1502368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1503368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15049024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1505368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15069024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1507368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1508368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15099024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 151477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1515368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 15164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 151777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 151877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 151977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 15204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 152177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 15220fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 15278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 15288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1529a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1530a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 15314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 15334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 15344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 15354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 15364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 15374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1538c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 153964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 154064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1541c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1542c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1543c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 154464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 15454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 15464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 15476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 15480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 15490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 15506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 15516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 155264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 155364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 155464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 155564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 155664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 155764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1558bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1559bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1560bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1561bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1562bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 15654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1577b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1578b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1579b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 15821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 15831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 15841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 15851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 15861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 15871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 15881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 15892f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 15901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1591b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1601fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 160383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 160583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16068b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 160783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1608b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1611c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 16161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 16171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 16181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 16191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 16201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 16211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 16221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 16231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 16241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 16251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 16271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 16281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 16291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 16301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 16311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 16341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 16386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 16391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1640fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1641b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1642fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1643c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 16441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 16451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 16461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 16471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 16481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 16491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1650e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 16511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 16521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 16531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 16541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 16551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 16561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 16571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 16581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 16591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 16601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 16661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1677c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 16795753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 16805753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 16815753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 16825753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1684c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 16868b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 16878b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 16888b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 16898b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16908b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 16918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 16928b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1693c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1696b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1697b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1698c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1700c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1701b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1702b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1703b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1704b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1705b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1706b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1707b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1708b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1709c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1710b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1712c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1713c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1714b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1715b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1716b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1717b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1718b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1719b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 17283656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1730b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1731b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1732b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void removeChip(RecipientChip chip) { 1733b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1734b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1736b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1737b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int toDelete = spanEnd; 1738b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1739b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1742c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Always remove trailing spaces when removing a chip. 17448b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira toDelete++; 1746c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira text.delete(spanStart, toDelete); 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1751c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1753c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1759b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1766b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1767b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira editable.insert(0, chipText); 1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1772b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // There may be a space to replace with this chip's new associated 1773b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // space. Check for it. 1774b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int toReplace = end; 1775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1776b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira && editable.charAt(toReplace) == ' ') { 1777b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira toReplace++; 1778b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1779b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira editable.replace(start, toReplace, chipText); 1780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1784c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1795b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1799c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1800b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1802368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1803368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1804368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1805368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1806311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1807311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1808311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1809311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1810311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1811311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1812e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1813e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1814e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18151e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18161e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18171e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18181e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18191e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 18211e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 18221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 18231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 18241e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18251e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 18261e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 18271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 18291e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 18311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1832368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1833e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1834e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 18355753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 18365753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 18375753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1838e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1839e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1840e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1841e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1842e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1843e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1844054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1846054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 184776ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1848054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1849054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1850054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1851054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1852054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1853054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 18633b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 18721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1875e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 187722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1879e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 188077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 188177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 188277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 188377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 188477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 188577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 188677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 188777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 188877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 188977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 189077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 189177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 189277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 189377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 189477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 189577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 189677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 189777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 189877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 189977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 19006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 190177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 190277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 190377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 190477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 190577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 190677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 190777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 190877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 19098659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 191077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 191177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 191277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 191377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 191477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 191577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 19161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 191777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 191877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 19191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 19201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 192177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 192377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 192477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 192577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 192677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 192977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 193077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 193177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 193277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 193377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 193477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 193577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 193677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 193777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 193877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 193977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1940b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1941b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1942b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 19436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 19446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1945b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 19466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 194777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 194877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 194977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 195177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 195277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 195377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 195477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 195677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 195877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 196077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 196177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 196277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 196377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 196477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 196577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 196677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 196777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 196877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 196977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 197077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 197177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 19728659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 197377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 197477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 197577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 197677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 19771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 197877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 197977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 19801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 19811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 198277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 198377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 198477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 198577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 198677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 198777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 198877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 198977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 199377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1995b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 19966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 19976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 19986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 19996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 20006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 20016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 20026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 20036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 20046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 20056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 20066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2007b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2008b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2009b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2010b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2011b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2012b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2013b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2014b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2015b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2016b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2017b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2018b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2019b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2020b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2021b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2022b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2023b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2024b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2025b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2026b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2027b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 2028b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 2029b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 2030b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2031b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2032b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2033b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2034b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2035b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2036b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2037b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2038b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2039b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2040b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2041b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2042b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2043b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2044b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2045b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2046b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2047b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2048b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2049b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2050b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2051b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2052b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2053b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2054b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2055b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2056b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2057b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2058b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2059b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2060b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2061b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2062b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2063b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2064b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2065b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2066b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2067b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2068b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2069b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2070b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2071b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2072b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2073b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2075