RecipientEditTextView.java revision 49ce9866ae314b3513e17008f107ceded23f9cf0
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; 2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription; 22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 39572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 47c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 53c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 55a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 56b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 58c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 63572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 64416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 67cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 68b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 69c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 70156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 72e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 73416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 74c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 75b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 76a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 78b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 89b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 90b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 117c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 119054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 120a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 124e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 128045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 134156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 136c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 14105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 142c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 143e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 144dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 15077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 154bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 166a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 176416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 198a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira private Runnable mDelayedShrink = new Runnable() { 199a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 200a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira @Override 201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira public void run() { 202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira shrink(); 203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira }; 206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 2072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 2082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 20977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 21077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 21177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 213e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 215e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2160436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 221368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 222e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 226368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 232c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 234156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 235156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 236156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 237156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2456ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 246b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 247ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 248cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 249ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 251ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 252ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 253ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 254ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 26183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 26283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 26383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 26483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 272d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 2827c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 2837c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira public Parcelable onSaveInstanceState() { 2847c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 2857c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira clearSelectedChip(); 2867c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira return super.onSaveInstanceState(); 2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira } 2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 291c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 3034f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 30405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 307c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 308a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 312a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 313a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 314a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 317c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 318d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 319d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 320b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 321d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 325416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 330b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 331b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3355a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3365a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 341a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 342a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 351001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 352a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 356001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 361a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3628a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) { 369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 374090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 38577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 38677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 38777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 38877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 395c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 396c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 397c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 398c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 42377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 42497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 42597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 42697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 428f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 429f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 430f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 431f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 442045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 443045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 444045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 445b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 446b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira mChipBackground : mInvalidChipBackground; 447045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 448045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 449e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 453e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4554221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4564221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4574221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4584221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4601a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4611a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 469045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 470045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 471045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 472045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 48490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 48590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 505ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5079024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 5089024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 510e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 51197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 512379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 51397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 52597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 52697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 534c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 53777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 541e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 544045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 55077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 552c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 55377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 558045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 559045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5605519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5615519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 563c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5645519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 565416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 56697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 56797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 568f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 569f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5804f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5814f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5824f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5844f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5871426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5891426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 592b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 59443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 595045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 596d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 611a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 612a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 613a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 640c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 64153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 64253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 64353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 64653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6477bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 64877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 651416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 652416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 656416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 659c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 660c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 661a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 662a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 663a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 665a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 678a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6877bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 7100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 71222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 72277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 72722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 73077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 732a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 74522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 781d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 782a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira String destText = createDisplayText(entry); 783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 784b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 79822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 79922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 80022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 80477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 814454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 815454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8196ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8266ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 828a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 845490556a764a879cd0eaff358e90705cc1335c92eErik } 846d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 848454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 865c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 89195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 89295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 89395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 89495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 90695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 912d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 917e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 918e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 919e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 947045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 949045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 950045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 958dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 979054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 98349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ListAdapter adapter = getAdapter(); 98449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (adapter != null && adapter.getCount() > 0 && enoughToFilter() 98549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && end == getSelectionEnd()) { 986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (editable.length() > tokenEnd + 1) { 99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 99449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 99549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 997a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 998b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 10011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1002d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 1003d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1004d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 100571fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10065ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10075ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 100949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Only dismiss the dropdown if it is related to the text we 101049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // just committed. 101149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // For paste, it may not be as there are possibly multiple 101249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // tokens being added. 101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (end == getSelectionEnd()) { 101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira dismissDropDown(); 101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1018d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1019d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1021d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10234031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10244031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1025f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1027f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1028f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1030f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1037c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1039c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1040c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1042c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1045f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1046a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1050f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 106005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1062e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1063e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1064e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 106539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 106639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 106739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 106839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1073e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10753b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10763b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10773b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10783b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 1079a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1080a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 1081a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1083054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 108405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 108505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 11042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 11064031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 11074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1108ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 112849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (enoughToFilter() && !isCompletedToken(text)) { 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 114249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 114349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 114449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (TextUtils.isEmpty(text)) { 114549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Check to see if this is a completed token before filtering. 114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = text.length(); 114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String token = text.toString().substring(start, end).trim(); 115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (!TextUtils.isEmpty(token)) { 115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = token.charAt(token.length() - 1); 115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 116336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1176d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1177d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1178d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1179d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11836caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11860436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 11978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1205416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 12065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 12075753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 12085753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1217416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1218416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1219c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1220416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1226c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1230e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 123177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1232416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1234368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1235e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1236e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1250e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1251e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1252e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1253e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1259b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1266a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1272e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 12934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 12944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1298c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 13074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 13084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1313c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1317b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1318b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1319b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 13274031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ String createDisplayText(RecipientEntry entry) { 13288659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13298659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13308659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13318659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13328659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1333a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1334a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1335a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1336c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1337c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1338c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1339c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1340a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13418659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = token.toString(); 13438659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String trimmedDisplayText = displayText.trim(); 13448659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13458659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 13468659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira .terminateToken(displayText) : displayText; 13478659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 13488659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1349fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 13508659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = createDisplayText(entry); 1351a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1352a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1353a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1355b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = displayText.length()-1; 1356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1370c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1371c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 13748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 13758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 13820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 13830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 13841e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 13851e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 13861e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 13944221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 13954221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 13964221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 13974221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1398f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 14020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 14030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 14040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 14060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 14070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 14080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 14090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1410c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1411c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 14120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 14130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 14140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 14150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 14160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 14180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 14237a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 14247a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14257a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14267a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 14277a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 143283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 143383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 143483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 143583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 14364031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 14374031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ RecipientChip[] getSortedRecipients() { 14386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 14396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 14406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 14416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 14426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 14446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 14456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 14466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 14476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 14486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 14496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 14506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 14516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 14566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 14576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 146283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 14637a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14647a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14657a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 14667a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 14744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 14794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 14834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 14888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 14894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 14914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1494a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1495a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 149622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 149722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 149822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 149922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 150022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 15018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1502045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1503045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1504045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 15058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1506a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1507a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1508bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1509bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1510bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1511bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 15126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 15146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 15156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 151783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 15180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 152283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1524c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1525c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1526c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1527c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1528c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1529c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 15304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 15314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 153322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 153422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 153522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 153622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 153722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 153822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 15426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1544368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 15454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 15460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 15470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1550368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1554368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1555368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1557368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1559368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1560368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 156677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1567368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 156977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 157077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 157177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 157377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 15740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 15754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 15798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 15808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1581a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1582a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 15834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 15844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 15854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 15864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 15874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 15884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 15894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1590c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 159164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 159264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1593c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1594c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1595c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 159664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 15974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 15984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 15996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 16000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 16010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 16026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 16036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 160464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 160564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 160664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 160764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 160864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 160964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1610bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1611bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1612bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1613bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1614bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 16154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1622b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1624b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1625b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1629b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1630b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1631b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1633b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 16341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 16371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 16381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 16391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 16401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 16412f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 16421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1643b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1644b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1653fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 165583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 165783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16588b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 165983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1663c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16656ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 16741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 16761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 16771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 16791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 16801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 16811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 16821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 16831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 16861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 16881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 16891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 16906ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 16911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1692fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1694fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1695c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1702e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 17181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1728b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1729c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1730b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 17315753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 17325753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 17335753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 17345753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1736c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1737b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17388b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 17398b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 17408b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 17418b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17428b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 17438b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 17448b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1752c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1759ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1765c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1767b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1773b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 17813656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1785ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1786ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1791b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1795b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1797b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1798b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1799b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1800b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1802b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1810b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1811b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1821b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1822a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1823a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1824a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1825a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1826a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1827a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1828a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1829a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1830a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1831a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1832a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1833a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1834a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1835a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1836a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1837b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1838b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1840b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1841b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1842b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1844b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1845c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1846b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1847b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1848b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1849b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1852b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1853b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1854b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1857c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1861368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1862368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1863368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1864368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1865311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1866311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1867311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1868311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1869311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1870311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 18801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 18811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 18821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 18831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 18851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 18861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 18881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 18901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1891368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 18945753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 18955753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 18965753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1903054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1905054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 190676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1907054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1908054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1909054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1910054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1911054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1912054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 19223b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1925e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1927e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 19311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1932e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 193622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 193977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 194049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira @Override 194149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira public boolean onTextContextMenuItem(int id) { 194249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (id == android.R.id.paste) { 194349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira removeTextChangedListener(mTextWatcher); 194449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 194549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Context.CLIPBOARD_SERVICE); 194649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipData clip = clipboard.getPrimaryClip(); 194749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (clip != null 194849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { 194949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira for (int i = 0; i < clip.getItemCount(); i++) { 195049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira CharSequence paste = clip.getItemAt(i).getText(); 195149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (paste != null) { 195249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = getSelectionStart(); 195349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = getSelectionEnd(); 195449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 195549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (start >= 0 && end >= 0 && start != end) { 195649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira editable.append(paste, start, end); 195749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } else { 195849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira editable.insert(end, paste); 195949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira handlePaste(); 196149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira mHandler.post(mAddTextWatcher); 196549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return true; 196649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return super.onTextContextMenuItem(id); 196849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 196949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 197049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 197149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */void handlePaste() { 197249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String text = getText().toString(); 197349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 197449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String lastAddress = text.substring(originalTokenStart); 197549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenStart = originalTokenStart; 197649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int prevTokenStart = tokenStart; 197749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip findChip = null; 197849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != 0) { 197949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // There are things before this! 198049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart != 0 && findChip == null) { 198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira prevTokenStart = tokenStart; 198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira findChip = findChip(tokenStart); 198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != originalTokenStart) { 198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (findChip != null) { 198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = prevTokenStart; 198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenEnd; 199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip createdChip; 199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart < originalTokenStart) { 199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart)); 199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira createdChip = findChip(tokenStart); 199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // +1 for the space at the end. 199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 199749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 200049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Take a look at the last token. If the token has been completed with a 200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // commit character, create a chip. 200249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (isCompletedToken(lastAddress)) { 200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(editable.toString().indexOf(lastAddress, originalTokenStart), editable 200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira .length(), editable); 200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd >= length()) { 201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // This token had not only an end token character, but also a space 201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // separating it from the next token. 202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 202677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 202777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 202877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 202977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 203077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 203177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 203277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 203377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 203477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 203577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 203677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 203777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 203877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 203977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 204077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 204177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 204277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 204377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 204477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 20456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 204677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 204777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 204877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 204977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 205077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 205177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 205277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 205377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 20548659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 205577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 205677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 205777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 205877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 205977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 206077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 20611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 20641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 20651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 2085b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 20862e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira oldText.removeSpan(chip); 2087b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 20886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 20896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 2090b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 20916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 20962e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira setText(text); 209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 209977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 210177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 21158659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 21201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 21241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 212577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 212677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 213077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2138b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 21396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 21406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 21416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 21426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 21436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 21446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 21456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 21466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 21476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2150b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 2171b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 2172b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2174b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2175b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2176b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2177b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2179b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2180b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2181b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2182b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2183b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2211b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2212b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2213b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2214b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2215b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2216b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 221777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2218