RecipientEditTextView.java revision a1ecf3cc31e72b56d51690ae6524e8a26e9dd358
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 2817c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 2827c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira public Parcelable onSaveInstanceState() { 2837c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 2847c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira clearSelectedChip(); 2857c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira return super.onSaveInstanceState(); 2867c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira } 2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 288c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 291c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 296001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 3024f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 30305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 304c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 307a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 308c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 310a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 312a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 313a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3146ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 315c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 317d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 318d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 319b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 320d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 324416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3254fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 328b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 330b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 331b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3345a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3355a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 338a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 341a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 342a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 348001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 351a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 352001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 356001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 358a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3618a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 362a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 364001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 366a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) { 368001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 373090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 374001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 38477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 38577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 38677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 38777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 38877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 393e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 394c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 395c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 396c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 397c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 398c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 401e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 405e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 42277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 42397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 42497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 42597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 427f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 428f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 429f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 430f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 431f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 440045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 442045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 443045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 444b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 445b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira mChipBackground : mInvalidChipBackground; 446045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 447045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 448e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 452e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4544221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4554221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4564221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4574221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4591a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4601a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 468045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 469045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 470045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 471045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4759024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 48490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5069024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 5079024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 509e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 51097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 511379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 51297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 51997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 52597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 5261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 533c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 53677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 540e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 543045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 54977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 55277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 557045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 558045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5595519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5605519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 562c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5635519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 56597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 56697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 567f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 568f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5794f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5804f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5814f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5834f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5861426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5881426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 590b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 59343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 594045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 595d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 596b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 610a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 611a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 612a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 613a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 615ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 626bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 638c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 64053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 64153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 64253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 64353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6467bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 64777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 648416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 651416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 652416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 656416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 658c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 659c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 660a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 661a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 662a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 663a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 66553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 692a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 7090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 71122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 72177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 72622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 72877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 74422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 774b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 780d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 781a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira String destText = createDisplayText(entry); 782d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 783b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 784d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 79722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 79822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 79922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 80377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 807d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8126ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 813454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 814454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8186ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8196ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8256ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8266ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 827a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 830a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 844490556a764a879cd0eaff358e90705cc1335c92eErik } 845d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8466ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 847454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 848a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8526ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 864c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 881c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 889c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 89095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 89195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 89295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 89395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 90595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 906c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 911d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 914e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 917e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 918e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 919e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 920c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 92195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 922e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 946045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 947045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 949045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 950045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 955045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 957dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 958e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 978054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 982001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 983e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 989a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (editable.length() > tokenEnd && editable.charAt(tokenEnd) == ',') { 990a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira tokenEnd++; 991a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 992b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 9951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 996d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 997d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 998d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 99971fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10005ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10015ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1002d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 1003d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 1004f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1006d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1007d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1009d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1010d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10114031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10124031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1013f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10144031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1015f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1017f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1018f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1019f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1020f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1021f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1022f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1023f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1024f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1025c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1027c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1028c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1029c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1030c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1034a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1037f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1038f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1039f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1040e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10411e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10421e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10431e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10441e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10451e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10461e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10471e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 104805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10491e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1050e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1051e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 105339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 105439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 105539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 105639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 105739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1058e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1059e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1060e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1061e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1062e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10633b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10643b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10653b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10663b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 1067a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1068a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 1069a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 10703b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1071054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 107205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 107305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1079c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1081b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1082b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 10922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 10932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 10944031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10954031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1096ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1099b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1100b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1101b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1102b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1103b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1104b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1105b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1106b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1132b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 113536d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1148d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1149d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1150d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1151d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1153d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11556caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11580436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1171b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1173b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1177416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 11785753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 11795753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 11805753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1189416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1190416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1191c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1192416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1193416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1194416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1196b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1197b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1198c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1202e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 120377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1204416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1206368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1207e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1208e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1216b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1222e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1223e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1224e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1225e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1226e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1227e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1238a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1244e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1253c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1254c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1255c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1256c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1257c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 12654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 12664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1268c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 12694fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1270c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12774fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 12784fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 12794fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 12804fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 12814fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 12824fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 12834fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1285c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1289b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1290b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1291b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1297c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12984031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 12994031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ String createDisplayText(RecipientEntry entry) { 13008659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13018659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13028659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13038659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13048659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1305a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1306a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1307a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1308c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1309c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1310c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1311c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1312a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13138659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 13148659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = token.toString(); 13158659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String trimmedDisplayText = displayText.trim(); 13168659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13178659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 13188659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira .terminateToken(displayText) : displayText; 13198659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 13208659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1321fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 13228659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = createDisplayText(entry); 1323a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1324a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1325a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1327b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = displayText.length()-1; 1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1339c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 13468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 13478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 13540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 13550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 13561e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 13571e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 13581e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1363c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 13664221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 13674221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 13684221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 13694221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1370f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1371c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1372c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 13740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 13750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 13760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 13780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 13790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 13800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 13810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1382c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1383c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 13840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 13850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 13860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 13870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 13880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 13900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 13957a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 13967a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 13977a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 13987a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 13997a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 140483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 140583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 140683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 140783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 14084031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 14094031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ RecipientChip[] getSortedRecipients() { 14106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 14116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 14126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 14136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 14146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 14166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 14176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 14186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 14196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 14206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 14216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 14226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 14236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 14286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 14296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 143483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 14357a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14367a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14377a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 14387a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1441c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1442c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 14464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 14514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 14554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 14608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 14614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 14634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1466a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1467a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 146822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 146922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 147022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 147122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 147222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 14738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1474045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1475045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1476045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 14778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1478a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1479a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1480bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1481bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1482bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1483bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 148983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 14900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 14910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 14924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 149483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 14954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1496c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1497c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1498c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1499c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1500c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1501c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 15024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 150522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 150622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 150722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 150822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 150922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 151022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 15146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 15154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1516368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 15174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 15180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 15190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15200fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1522368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1526368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1527368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1529368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15309024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1531368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1532368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 153877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1539368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 15404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 154177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 154277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 154377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 15444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 154577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 15460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 15474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 15518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 15528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1553a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1554a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 15554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 15564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 15574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 15584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 15594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 15604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 15614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1562c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 156364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 156464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1565c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1566c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1567c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 156864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 15694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 15704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 15716f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 15720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 15730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 15746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 15756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 157664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 157764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 157864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 157964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 158064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 158164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1582bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1583bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1584bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1585bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1586bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 15874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 15894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 16061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 16071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 16081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 16091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 16101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 16111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 16121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 16132f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 16141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1616b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1617b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1618b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1619b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1620b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1621b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1622b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1624b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1625fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 162783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 162983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16308b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 163183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1632b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1635c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16376ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1638b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1639b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 16401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 16411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 16421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 16431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 16441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 16451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 16461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 16471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 16481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 16491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 16511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 16521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 16531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 16541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 16551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 16581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 16601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 16626ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1664fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1665b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1666fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1667c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1674e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 16761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 16771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 16781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 16791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 16801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 16811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 16821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 16831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 16841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 16861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 16881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 16891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 16901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1692b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1696b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1697b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1698b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1700b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1701c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1702b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 17035753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 17045753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 17055753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 17065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1707b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1708c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1709b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17108b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 17118b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 17128b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 17138b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17148b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 17158b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 17168b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1717c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1718b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1719b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1722c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1724c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1728b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1730b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1731ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 1732b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1733b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1734c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1736b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1737c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1738c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1739b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1742b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 17533656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1757ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1758ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1759b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1762b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1763b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1766b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1767b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1769b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1770b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1771b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1772b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1773b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1774b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1777c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1779c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1788c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1794a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1795a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1796a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1797a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1798a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1799a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1800a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1801a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1802a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1803a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1804a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1805a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1806a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1807a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1808a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1809b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1810b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1816b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1821b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1826b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1831b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1833368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1834368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1835368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1836368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1837311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1838311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1839311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1840311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1841311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1842311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1843e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1844e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18461e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18471e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18481e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18491e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18501e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18511e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 18521e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 18531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 18541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 18551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 18571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 18581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 18601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 18621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1863368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 18665753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 18675753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 18685753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1875054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1877054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 187876ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1879054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1880054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1881054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1882054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1883054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1884054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1889e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 18943b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 19031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 190822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 191177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 191277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 191377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 191477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 191577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 191677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 191777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 191877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 191977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 192277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 192377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 192477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 192577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 192677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 192877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 192977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 193077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 19316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 193277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 193377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 193477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 193577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 193677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 193777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 193877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 193977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 19408659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 194177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 194277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 194377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 194477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 194577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 194677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 19471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 194877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 194977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 19501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 19511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 195277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 195477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 195577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 195677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 195777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 195977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 196077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 196177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 196277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 196377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 196477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 196577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 196677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 196777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 196877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 196977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 197077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1971b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1972b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1973b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 19746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 19756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1976b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 19776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 197877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 197977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 198077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 198177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 198277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 198377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 198477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 198577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 198677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 198777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 198877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 198977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 199177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 199277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 199377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 199477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 199577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 199677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 199777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 199877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 199977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 200077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 200177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 200277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 20038659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 200477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 200577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 200677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 200777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 20081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 200977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 201077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 20111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 20121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 201377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 201477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 201577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 201677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 201777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 201877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 201977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 202077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 202177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 202277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 202377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 202477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 202577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2026b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 20276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 20286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 20296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 20306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 20316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 20326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 20336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 20346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 20356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 20366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 20376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2038b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2039b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2040b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2041b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2042b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2043b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2044b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2045b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2046b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2047b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2048b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2049b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2050b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2051b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2052b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2053b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2054b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2055b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2056b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2057b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2058b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 2059b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 2060b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 2061b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2062b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2063b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2064b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2065b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2066b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2067b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2068b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2069b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2070b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2071b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2072b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2073b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2074b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2075b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2076b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2077b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2078b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2079b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2080b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2081b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2082b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2083b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2084b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2085b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2086b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2087b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2088b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2089b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2090b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2091b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2092b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2093b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2094b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2095b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2096b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2097b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2098b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2099b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2100b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2101b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2102b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2103b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2104b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2106