19159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/* 2194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy 39159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Copyright (C) 2011 The Android Open Source Project 49159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * 59159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 69159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * you may not use this file except in compliance with the License. 79159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * You may obtain a copy of the License at 89159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * 99159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 109159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * 119159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Unless required by applicable law or agreed to in writing, software 129159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 139159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 149159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * See the License for the specific language governing permissions and 159159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * limitations under the License. 169159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */ 179159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 189159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereirapackage com.android.ex.chips; 199159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 20ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Caoimport android.annotation.TargetApi; 21c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sappersteinimport android.app.Activity; 22e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadamimport android.app.AlertDialog; 23c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sappersteinimport android.app.DialogFragment; 241d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipData; 2520c9d620e79ae28994856541761a951074551518Mindy Pereiraimport android.content.ClipDescription; 261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipboardManager; 279159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.content.Context; 28b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadamimport android.content.DialogInterface; 2952c441e2c03e0f48572348953b985a4bf989c057Mindy Pereiraimport android.content.res.Resources; 3022faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereiraimport android.content.res.TypedArray; 31cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Bitmap; 322bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.BitmapFactory; 33b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedyimport android.graphics.BitmapShader; 34cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Canvas; 35b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedyimport android.graphics.Color; 362bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.Matrix; 37b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Linimport android.graphics.Paint; 38b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedyimport android.graphics.Paint.Style; 39e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.graphics.Point; 40cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Rect; 412bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.RectF; 42b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedyimport android.graphics.Shader.TileMode; 43cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 449159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.graphics.drawable.Drawable; 454db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Caoimport android.graphics.drawable.StateListDrawable; 461852931de1e24e77cb708f4ba010eaa269426657Mindy Pereiraimport android.os.AsyncTask; 471650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedyimport android.os.Build; 48b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadamimport android.os.Bundle; 49007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Handler; 5078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedyimport android.os.Looper; 51007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Message; 52dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereiraimport android.os.Parcelable; 53ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Caoimport android.support.annotation.NonNull; 549159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.text.Editable; 5561b48ccefc655549802556947eb8cf3959c6ddadGilles Debunneimport android.text.InputType; 56cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Layout; 57cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spannable; 58cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.SpannableString; 590e128968f242866568bba0f833bb17ffda127f07Scott Kennedyimport android.text.SpannableStringBuilder; 60cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spanned; 61cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextPaint; 62cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextUtils; 63b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereiraimport android.text.TextWatcher; 64cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.method.QwertyKeyListener; 653bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Token; 663bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 679159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.util.AttributeSet; 68cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.util.Log; 69fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.ActionMode; 70aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadlerimport android.view.ActionMode.Callback; 71e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.view.DragEvent; 721d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.view.GestureDetector; 73cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.KeyEvent; 74750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.view.LayoutInflater; 75fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.Menu; 76fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.MenuItem; 77cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.MotionEvent; 78cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.View; 79c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.view.ViewParent; 8004f06ea723b0445428d3f563334d653299a73a74Jin Caoimport android.view.accessibility.AccessibilityEvent; 8104f06ea723b0445428d3f563334d653299a73a74Jin Caoimport android.view.accessibility.AccessibilityManager; 8293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereiraimport android.view.inputmethod.EditorInfo; 8393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereiraimport android.view.inputmethod.InputConnection; 84cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView; 85cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 86093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindypimport android.widget.Filterable; 8795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereiraimport android.widget.ListAdapter; 88cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.ListPopupWindow; 89007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.widget.ListView; 909159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.widget.MultiAutoCompleteTextView; 91939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Caoimport android.widget.PopupWindow; 92c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.widget.ScrollView; 93750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.widget.TextView; 947537f840506bcb642bed9dc1c2bdcf6d31c6b2a7Daisuke Miyakawa 95f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovicimport com.android.ex.chips.DropdownChipLayouter.PermissionRequestDismissedListener; 9616923ee63a79fce4be3f62b08bcd1f80617c1205mindypimport com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback; 97194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.DrawableRecipientChip; 98194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.InvisibleRecipientChip; 996c2a016fb00482339dc66b93822042f9f9e2424fJin Caoimport com.android.ex.chips.recipientchip.ReplacementDrawableSpan; 100194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.VisibleRecipientChip; 101cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 10276f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.ArrayList; 10376f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Arrays; 10476f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Collections; 10576f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Comparator; 106e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadamimport java.util.HashSet; 10776f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.List; 10876f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Map; 10976f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Set; 11076f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert 1119159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/** 1129159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 1139159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * that use the new Chips UI for addressing a message to recipients. 1149159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */ 11595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 1161d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 117c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein GestureDetector.OnGestureListener, TextView.OnEditorActionListener, 118f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic DropdownChipLayouter.ChipDeleteListener, PermissionRequestDismissedListener { 119a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private static final String TAG = "RecipientEditTextView"; 120cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 121aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 122aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 123aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 12403e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy private static final String SEPARATOR = String.valueOf(COMMIT_CHAR_COMMA) 12503e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy + String.valueOf(COMMIT_CHAR_SPACE); 12603e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy 127f11483ad7d1e7abbce9d59ecf01b4b904a777b05Scott Kennedy private static final int DISMISS = "dismiss".hashCode(); 128aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private static final long DISMISS_DELAY = 300; 129aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira 13012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira // TODO: get correct number/ algorithm from with UX. 131f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Visible for testing. 132f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira /*package*/ static final int CHIP_LIMIT = 2; 133f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 134f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira private static final int MAX_CHIPS_PARSED = 50; 135b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam public static final String STATE_TEXT_VIEW = "savedTextView"; 136b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam public static final String STATE_CURRENT_WARNING_TEXT = "savedCurrentWarningText"; 13712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 138cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang private int mUnselectedChipTextColor; 139cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang private int mUnselectedChipBackgroundColor; 140aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira 141fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao // Work variables to avoid re-allocation on every typed character. 142fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao private final Rect mRect = new Rect(); 143fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao private final int[] mCoords = new int[2]; 144fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao 145aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Resources for displaying chips. 146cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira private Drawable mChipBackground = null; 147cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira private Drawable mChipDelete = null; 148aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private Drawable mInvalidChipBackground; 149aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira 150a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao // Possible attr overrides 151aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private float mChipHeight; 152aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private float mChipFontSize; 153ffd270a55d80db66bd6a1d7b786443fdc00af372Mindy Pereira private float mLineSpacingExtra; 15458d541de27090b4bd55746368b3b3b1486a7c094Jin Cao private int mChipTextStartPadding; 15558d541de27090b4bd55746368b3b3b1486a7c094Jin Cao private int mChipTextEndPadding; 15615259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy private final int mTextHeight; 157a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private boolean mDisableDelete; 158a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private int mMaxLines; 159e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private int mWarningIconHeight; 160cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 161b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /** 162b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * Enumerator for avatar position. See attr.xml for more details. 163c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein * 0 for end, 1 for start. 164b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin */ 165b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin private int mAvatarPosition; 166c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein private static final int AVATAR_POSITION_END = 0; 167c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein private static final int AVATAR_POSITION_START = 1; 168b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 169809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao private Paint mWorkPaint = new Paint(); 170809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao 171cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira private Tokenizer mTokenizer; 172aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private Validator mValidator; 173a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private Handler mHandler; 174a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private TextWatcher mTextWatcher; 1756e96c08f77c783d7eb7a926151f16f95255205adMichael Berberet protected DropdownChipLayouter mDropdownChipLayouter; 176cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 17793d69f407e258e42705c4cc706d6705f45d48761Jin Cao private View mDropdownAnchor = this; 178a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private ListPopupWindow mAlternatesPopup; 179a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private ListPopupWindow mAddressPopup; 180a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private View mAlternatePopupAnchor; 181a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private OnItemClickListener mAlternatesListener; 182cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 183a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private DrawableRecipientChip mSelectedChip; 1842bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira private Bitmap mDefaultContactPhoto; 185e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private Bitmap mWarningIcon; 1866c2a016fb00482339dc66b93822042f9f9e2424fJin Cao private ReplacementDrawableSpan mMoreChip; 187750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereira private TextView mMoreItem; 18812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 189b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao private int mCurrentSuggestionCount; 190b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao 19120c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy // VisibleForTesting 19220c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy final ArrayList<String> mPendingChips = new ArrayList<String>(); 193f97eb41a7946f2c3013ac74f2451b78070531125Mindy Pereira 19402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira private int mPendingChipsCount = 0; 195a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private int mCheckedItem; 1961163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz private boolean mNoChipMode = false; 197a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private boolean mShouldShrink = true; 198654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy private boolean mRequiresShrinkWhenNotGone = false; 1994de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao 20020c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy // VisibleForTesting 201194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy ArrayList<DrawableRecipientChip> mTemporaryRecipients; 2021852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira 2031a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz private ArrayList<DrawableRecipientChip> mHiddenSpans; 2043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 2051d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira // Chip copy fields. 2061d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira private GestureDetector mGestureDetector; 2071d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 208aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Obtain the enclosing scroll view, if it exists, so that the view can be 209aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // scrolled to show the last line of chips content. 210c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira private ScrollView mScrollView; 211aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira private boolean mTriedGettingScrollView; 212e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham private boolean mDragEnabled = false; 213e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 214a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao private boolean mAttachedToWindow; 2154491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor 21679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 21779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira @Override 21879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira public void run() { 21979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira if (mTextWatcher == null) { 22079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira mTextWatcher = new RecipientTextWatcher(); 22179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira addTextChangedListener(mTextWatcher); 22279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 22379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 22479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira }; 22579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira 2261852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 2271852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira 2282cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 2292cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira 2302cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira @Override 2312cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira public void run() { 2322cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira handlePendingChips(); 2332cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 2342cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira 2352cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira }; 2362cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira 237d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira private Runnable mDelayedShrink = new Runnable() { 238d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira 239d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira @Override 240d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira public void run() { 241d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira shrink(); 242d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira } 243d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira 244d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira }; 245d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira 2460f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein private RecipientEntryItemClickedListener mRecipientEntryItemClickedListener; 2470f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein 248ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz private RecipientChipAddedListener mRecipientChipAddedListener; 249c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko private RecipientChipDeletedListener mRecipientChipDeletedListener; 250c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko 251e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // A set of recipient addresses that are untrusted because they are outside of the user's 252e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // domain. We will show a warning for these addresses in the recipient chips. 253e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private Set<String> mUntrustedAddresses = new HashSet<>(); 254e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 255b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam private String mWarningTextTemplate = ""; 256e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private String mWarningTitle = ""; 257b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam // Text of the warning dialog currently being displayed. Empty if no dialog currently displayed. 258b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam private String mCurrentWarningText = ""; 259e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 260e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam /** 261e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * Sets this recipient edit text view to display warning icons in chips for the given addresses. 262e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * 263e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * @param untrustedAddresses The addresses to display warning icons for. 264e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * @param warningIcon The icon to show for each address. 265e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * @param warningIconHeight Height of the warning icon in 266b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam * @param warningTextTemplate Text to display when warning icon is clicked. 267e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * @param warningTitle Title to display for text when warning icon is clicked. 268e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam */ 269e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam public void setUntrustedAddressWarning( 270e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam Set<String> untrustedAddresses, 271e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam Bitmap warningIcon, 272e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam int warningIconHeight, 273b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam String warningTextTemplate, 274e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam String warningTitle) { 275e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mUntrustedAddresses = untrustedAddresses; 276e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWarningIcon = warningIcon; 277e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWarningIconHeight = warningIconHeight; 278b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam mWarningTextTemplate = warningTextTemplate; 279e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWarningTitle = warningTitle; 280e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 281e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 2820f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein public interface RecipientEntryItemClickedListener { 2830f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein /** 2840f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein * Callback that occurs whenever an auto-complete suggestion is clicked. 2850f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein * @param charactersTyped the number of characters typed by the user to provide the 2860f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein * auto-complete suggestions. 2870f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein * @param position the position in the dropdown list that the user clicked 2880f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein */ 2890f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein void onRecipientEntryItemClicked(int charactersTyped, int position); 2900f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein } 2910f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein 292f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic private PermissionsRequestItemClickedListener mPermissionsRequestItemClickedListener; 293f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 294f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic /** 295f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * Listener for handling clicks on the {@link RecipientEntry} that have 296f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * {@link RecipientEntry#ENTRY_TYPE_PERMISSION_REQUEST} type. 297f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic */ 298f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic public interface PermissionsRequestItemClickedListener { 299f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 300f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic /** 301f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * Callback that occurs when user clicks the item that asks user to grant permissions to 302f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * the app. 303f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * 304f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * @param view View that asks for permission. 305f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic */ 306f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic void onPermissionsRequestItemClicked(RecipientEditTextView view, String[] permissions); 307f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 308f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic /** 309f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * Callback that occurs when user dismisses the item that asks user to grant permissions to 310f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic * the app. 311f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic */ 312f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic void onPermissionRequestDismissed(); 313f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 314f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 315c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko /** 316c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko * Listener for handling deletion of chips in the recipient edit text. 317c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko */ 318c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko public interface RecipientChipDeletedListener { 319c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko /** 320c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko * Callback that occurs when a chip is deleted. 321c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko * @param entry RecipientEntry that contains information about the chip. 322c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko */ 323c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko void onRecipientChipDeleted(RecipientEntry entry); 324c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko } 325c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko 326ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz /** 327ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz * Listener for handling addition of chips in the recipient edit text. 328ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz */ 329ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz public interface RecipientChipAddedListener { 330ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz /** 331ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz * Callback that occurs when a chip is added. 332ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz * 333ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz * @param entry RecipientEntry that contains information about the chip. 334ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz */ 335ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz void onRecipientChipAdded(RecipientEntry entry); 336ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz } 337ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz 3389159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 3399159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira super(context, attrs); 34022faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira setChipDimensions(context, attrs); 34115259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy mTextHeight = calculateTextHeight(); 34295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 343939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao setupPopupWindow(mAlternatesPopup); 34401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira mAddressPopup = new ListPopupWindow(context); 345939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao setupPopupWindow(mAddressPopup); 34695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mAlternatesListener = new OnItemClickListener() { 34795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira @Override 34895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 34995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira long rowId) { 35021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 35195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 35295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira .getRecipientEntry(position)); 35395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 35421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira delayed.obj = mAlternatesPopup; 35595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 35695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira clearComposingText(); 35795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 35895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira }; 359e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 360b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira setOnItemClickListener(this); 361fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira setCustomSelectionActionModeCallback(this); 362007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira mHandler = new Handler() { 363007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira @Override 364007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira public void handleMessage(Message msg) { 365007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira if (msg.what == DISMISS) { 36695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 367007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira return; 368007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira } 369007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira super.handleMessage(msg); 370007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira } 371007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira }; 3724e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira mTextWatcher = new RecipientTextWatcher(); 3734e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira addTextChangedListener(mTextWatcher); 3741d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira mGestureDetector = new GestureDetector(context, this); 37593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira setOnEditorActionListener(this); 376b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 377a20a8d02c0b6a9440a61870ee6c7a6b6c92fbf4bJin Cao setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context)); 378b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 379b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 380939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao private void setupPopupWindow(ListPopupWindow popup) { 381939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao popup.setOnDismissListener(new PopupWindow.OnDismissListener() { 382939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao @Override 383939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao public void onDismiss() { 384939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao clearSelectedChip(); 385939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao } 386939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao }); 387939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao } 388939f5f1fb5b2d851a5b36fc9908c2c6420b193aeJin Cao 38915259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy private int calculateTextHeight() { 39015259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy final TextPaint paint = getPaint(); 39115259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy 392fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao mRect.setEmpty(); 39315259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy // First measure the bounds of a sample text. 39415259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy final String textHeightSample = "a"; 395fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao paint.getTextBounds(textHeightSample, 0, textHeightSample.length(), mRect); 39615259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy 397fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao mRect.left = 0; 398fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao mRect.right = 0; 39915259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy 400fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao return mRect.height(); 40115259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy } 40215259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy 4038af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) { 404b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin mDropdownChipLayouter = dropdownChipLayouter; 405a20a8d02c0b6a9440a61870ee6c7a6b6c92fbf4bJin Cao mDropdownChipLayouter.setDeleteListener(this); 406f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic mDropdownChipLayouter.setPermissionRequestDismissedListener(this); 40793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 40893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira 4090f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein public void setRecipientEntryItemClickedListener(RecipientEntryItemClickedListener listener) { 4100f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein mRecipientEntryItemClickedListener = listener; 4110f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein } 4120f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein 413f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic public void setPermissionsRequestItemClickedListener( 414f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic PermissionsRequestItemClickedListener listener) { 415f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic mPermissionsRequestItemClickedListener = listener; 416f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 417f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 418ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz public void setRecipientChipAddedListener(RecipientChipAddedListener listener) { 419ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz mRecipientChipAddedListener = listener; 420ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz } 421ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz 422c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko public void setRecipientChipDeletedListener(RecipientChipDeletedListener listener) { 423c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko mRecipientChipDeletedListener = listener; 424c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko } 425c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko 42693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira @Override 427fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler protected void onDetachedFromWindow() { 4282b2de7c3146a60d39d5d2078052b2c14b6496ef9Tony Mantler super.onDetachedFromWindow(); 429fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler mAttachedToWindow = false; 430fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler } 431fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler 432fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler @Override 433fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler protected void onAttachedToWindow() { 4342b2de7c3146a60d39d5d2078052b2c14b6496ef9Tony Mantler super.onAttachedToWindow(); 435fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler mAttachedToWindow = true; 43693d69f407e258e42705c4cc706d6705f45d48761Jin Cao 43793d69f407e258e42705c4cc706d6705f45d48761Jin Cao final int anchorId = getDropDownAnchor(); 43893d69f407e258e42705c4cc706d6705f45d48761Jin Cao if (anchorId != View.NO_ID) { 43993d69f407e258e42705c4cc706d6705f45d48761Jin Cao mDropdownAnchor = getRootView().findViewById(anchorId); 44093d69f407e258e42705c4cc706d6705f45d48761Jin Cao } 441fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler } 442fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler 443fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler @Override 444494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin public void setDropDownAnchor(int anchorId) { 445494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin super.setDropDownAnchor(anchorId); 446494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin if (anchorId != View.NO_ID) { 447494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin mDropdownAnchor = getRootView().findViewById(anchorId); 448494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin } 449494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin } 450494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin 451494bec8daac55c76707e140c71fb8783fb3a9323Kevin Lin @Override 45293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) { 45393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira if (action == EditorInfo.IME_ACTION_DONE) { 45493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira if (commitDefault()) { 45593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira return true; 45693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 45793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira if (mSelectedChip != null) { 45893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira clearSelectedChip(); 45993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira return true; 460ec8fce439b2bfc816c98bd454c56f8fd10ab0740Tavis Bohne } else if (hasFocus()) { 461ec8fce439b2bfc816c98bd454c56f8fd10ab0740Tavis Bohne if (focusNext()) { 462ec8fce439b2bfc816c98bd454c56f8fd10ab0740Tavis Bohne return true; 463ec8fce439b2bfc816c98bd454c56f8fd10ab0740Tavis Bohne } 46493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 46593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 46693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira return false; 46793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 46893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira 46993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira @Override 470ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) { 47193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira InputConnection connection = super.onCreateInputConnection(outAttrs); 47293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION; 47393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) { 47493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira // clear the existing action 47593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira outAttrs.imeOptions ^= imeActions; 47693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira // set the DONE action 47793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 47893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 47993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { 48093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; 48193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira } 4821af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy 4831af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy outAttrs.actionId = EditorInfo.IME_ACTION_DONE; 484a5461b72d228191efda5ef084455f3066d5648aeKaikai Wang 485a5461b72d228191efda5ef084455f3066d5648aeKaikai Wang // Custom action labels are discouraged in L; a checkmark icon is shown in place of the 486a5461b72d228191efda5ef084455f3066d5648aeKaikai Wang // custom text in this case. 4878f4326a7a29664a49c92292e2466975924e77d1eNick Kralevich outAttrs.actionLabel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? null : 488a5461b72d228191efda5ef084455f3066d5648aeKaikai Wang getContext().getString(R.string.action_label); 48993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira return connection; 490e33555f13a9b05d835cb860e2c30ef40af3c8502Erik } 4917afe160db4a48f66c964ced89e29e0b63b23c7c1Mindy Pereira 492194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy /*package*/ DrawableRecipientChip getLastChip() { 493194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip last = null; 494194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = getSortedRecipients(); 495aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira if (chips != null && chips.length > 0) { 496aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira last = chips[chips.length - 1]; 497e33555f13a9b05d835cb860e2c30ef40af3c8502Erik } 498aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira return last; 499fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira } 500fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira 5011248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai /** 5021248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai * @return The list of {@link RecipientEntry}s that have been selected by the user. 5031248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai */ 5041248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai public List<RecipientEntry> getSelectedRecipients() { 5051248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai DrawableRecipientChip[] chips = 5061248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai getText().getSpans(0, getText().length(), DrawableRecipientChip.class); 507ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao List<RecipientEntry> results = new ArrayList<RecipientEntry>(); 5081248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai if (chips == null) { 5091248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai return results; 5101248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai } 5111248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai 5121248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai for (DrawableRecipientChip c : chips) { 5131248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai results.add(c.getEntry()); 5141248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai } 5151248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai 5161248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai return results; 5171248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai } 5181248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai 5191a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz /** 5201a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz * @return The list of {@link RecipientEntry}s that have been selected by the user and also 5211a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz * hidden due to {@link #mMoreChip} span. 5221a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz */ 5231a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz public List<RecipientEntry> getAllRecipients() { 5241a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz List<RecipientEntry> results = getSelectedRecipients(); 5251a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz 5261a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz if (mHiddenSpans != null) { 5271a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz for (DrawableRecipientChip chip : mHiddenSpans) { 5281a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz results.add(chip.getEntry()); 5291a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz } 5301a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz } 5311a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz 5321a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz return results; 5331a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz } 5341a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz 535fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira @Override 536fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira public void onSelectionChanged(int start, int end) { 537fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira // When selection changes, see if it is inside the chips area. 538fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira // If so, move the cursor back after the chips again. 539ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao // Only exception is when we change the selection due to a selected chip. 540194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip last = getLastChip(); 541ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao if (mSelectedChip == null && last != null && start < getSpannable().getSpanEnd(last)) { 542aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Grab the last chip and set the cursor to after it. 543aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length())); 54405dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira } 54505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira super.onSelectionChanged(start, end); 54605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira } 54705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira 548dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira @Override 549dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira public void onRestoreInstanceState(Parcelable state) { 550b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam Bundle savedInstanceState = (Bundle) state; 551dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira if (!TextUtils.isEmpty(getText())) { 552dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira super.onRestoreInstanceState(null); 553dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira } else { 554b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam super.onRestoreInstanceState( 555b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam savedInstanceState.getParcelable(STATE_TEXT_VIEW)); 556b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam } 557b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam String savedWarningText = savedInstanceState.getString( 558b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam STATE_CURRENT_WARNING_TEXT); 559b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam if (!savedWarningText.isEmpty()) { 560b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam showWarningDialog(savedWarningText); 561dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira } 562dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira } 563dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira 564aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira @Override 565daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira public Parcelable onSaveInstanceState() { 566daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 567daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira clearSelectedChip(); 568b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam Bundle savedInstanceState = new Bundle(); 569b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam savedInstanceState.putParcelable(STATE_TEXT_VIEW, super.onSaveInstanceState()); 570b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam savedInstanceState.putString(STATE_CURRENT_WARNING_TEXT, mCurrentWarningText); 571b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam return savedInstanceState; 572daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira } 573daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira 57402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira /** 57502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira * Convenience method: Append the specified text slice to the TextView's 57602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 57702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira * not already editable. Commas are excluded as they are added automatically 57802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira * by the view. 57902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira */ 58002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira @Override 58102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira public void append(CharSequence text, int start, int end) { 582e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira // We don't care about watching text changes while appending. 583e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira if (mTextWatcher != null) { 584e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira removeTextChangedListener(mTextWatcher); 585e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira } 58602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira super.append(text, start, end); 58702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 5880124468e712b4098ea240b8d45f84e52826b293dmindyp String displayString = text.toString(); 58903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy 59003e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy if (!displayString.trim().endsWith(String.valueOf(COMMIT_CHAR_COMMA))) { 59103e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy // We have no separator, so we should add it 59203e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy super.append(SEPARATOR, 0, SEPARATOR.length()); 59303e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy displayString += SEPARATOR; 5940124468e712b4098ea240b8d45f84e52826b293dmindyp } 59503e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy 59620d7af71188a4a2d94c8d9edd7eff7879d6df4c4mindyp if (!TextUtils.isEmpty(displayString) 59702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 59802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira mPendingChipsCount++; 59903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy mPendingChips.add(displayString); 60002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira } 60102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira } 602abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp // Put a message on the queue to make sure we ALWAYS handle pending 603abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp // chips. 6042cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira if (mPendingChipsCount > 0) { 6052cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira postHandlePendingChips(); 6062cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 6074e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira mHandler.post(mAddTextWatcher); 60802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira } 60902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira 61005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira @Override 61105dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 612d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 61305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira if (!hasFocus) { 61412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira shrink(); 615fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira } else { 61612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira expand(); 617fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira } 6189159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira } 6199159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 6206b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang @Override 621ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public <T extends ListAdapter & Filterable> void setAdapter(@NonNull T adapter) { 622093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp super.setAdapter(adapter); 623b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter; 624b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() { 625b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin @Override 626b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin public void onChanged(List<RecipientEntry> entries) { 627f76bb008467aa6b86af54265b2976bb23d640252Mark Wei int suggestionCount = entries == null ? 0 : entries.size(); 628f76bb008467aa6b86af54265b2976bb23d640252Mark Wei 629b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // Scroll the chips field to the top of the screen so 630b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // that the user can see as many results as possible. 631b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin if (entries != null && entries.size() > 0) { 632b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin scrollBottomIntoView(); 633b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao // Here the current suggestion count is still the old one since we update 634b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao // the count at the bottom of this function. 635b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao if (mCurrentSuggestionCount == 0) { 636b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao // Announce the new number of possible choices for accessibility. 637f76bb008467aa6b86af54265b2976bb23d640252Mark Wei announceForAccessibilityCompat( 638f76bb008467aa6b86af54265b2976bb23d640252Mark Wei getSuggestionDropdownOpenedVerbalization(suggestionCount)); 639b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao } 640b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 64193d69f407e258e42705c4cc706d6705f45d48761Jin Cao 642626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde // Is the dropdown closing? 643626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde if ((entries == null || entries.size() == 0) 644626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde // Here the current suggestion count is still the old one since we update 645626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde // the count at the bottom of this function. 646c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko && mCurrentSuggestionCount != 0 647c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko // If there is no text, there's no need to know if no suggestions are 648c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko // available. 649c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko && getText().length() > 0) { 650626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde announceForAccessibilityCompat(getResources().getString( 651626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde R.string.accessbility_suggestion_dropdown_closed)); 652626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde } 653626f8cd53e91d43e98a4fe37ceedd43e1a5829b5Shri Borde 6548bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock if ((entries != null) 6558bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock && (entries.size() == 1) 6568bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock && (entries.get(0).getEntryType() == 6578bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock RecipientEntry.ENTRY_TYPE_PERMISSION_REQUEST)) { 6588bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock // Do nothing; showing a single permissions entry. Resizing not required. 6598bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock } else { 6608bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock // Set the dropdown height to be the remaining height from the anchor to the 6618bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock // bottom. 662087b818dae4783d7757c0bac151d60acb7692e4fGreg Bullock mDropdownAnchor.getLocationOnScreen(mCoords); 6638bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock getWindowVisibleDisplayFrame(mRect); 6648bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock setDropDownHeight(mRect.bottom - mCoords[1] - mDropdownAnchor.getHeight() - 6658bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock getDropDownVerticalOffset()); 6668bfbed87a9bf290eb6757f352fa39ed6766932e3Greg Bullock } 667b1aeb7b6e08b0b8dd548c4f0e5ac55844fd7f55dJin Cao 668f76bb008467aa6b86af54265b2976bb23d640252Mark Wei mCurrentSuggestionCount = suggestionCount; 669b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 670b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin }); 671b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter); 672093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp } 673093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp 674f76bb008467aa6b86af54265b2976bb23d640252Mark Wei /** 675f76bb008467aa6b86af54265b2976bb23d640252Mark Wei * Return the accessibility verbalization when the suggestion dropdown is opened. 676f76bb008467aa6b86af54265b2976bb23d640252Mark Wei */ 677f76bb008467aa6b86af54265b2976bb23d640252Mark Wei public String getSuggestionDropdownOpenedVerbalization(int suggestionCount) { 678f76bb008467aa6b86af54265b2976bb23d640252Mark Wei return getResources().getString(R.string.accessbility_suggestion_dropdown_opened); 679f76bb008467aa6b86af54265b2976bb23d640252Mark Wei } 680f76bb008467aa6b86af54265b2976bb23d640252Mark Wei 681ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 68204f06ea723b0445428d3f563334d653299a73a74Jin Cao private void announceForAccessibilityCompat(String text) { 6835037dea4aa3f04ff1878ddbdbd7cf4d215c426e3Scott Kennedy final AccessibilityManager accessibilityManager = 6845037dea4aa3f04ff1878ddbdbd7cf4d215c426e3Scott Kennedy (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 6855037dea4aa3f04ff1878ddbdbd7cf4d215c426e3Scott Kennedy final boolean isAccessibilityOn = accessibilityManager.isEnabled(); 6865037dea4aa3f04ff1878ddbdbd7cf4d215c426e3Scott Kennedy 6875037dea4aa3f04ff1878ddbdbd7cf4d215c426e3Scott Kennedy if (isAccessibilityOn && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 68804f06ea723b0445428d3f563334d653299a73a74Jin Cao final ViewParent parent = getParent(); 68904f06ea723b0445428d3f563334d653299a73a74Jin Cao if (parent != null) { 69004f06ea723b0445428d3f563334d653299a73a74Jin Cao AccessibilityEvent event = AccessibilityEvent.obtain( 69104f06ea723b0445428d3f563334d653299a73a74Jin Cao AccessibilityEvent.TYPE_ANNOUNCEMENT); 69204f06ea723b0445428d3f563334d653299a73a74Jin Cao onInitializeAccessibilityEvent(event); 69304f06ea723b0445428d3f563334d653299a73a74Jin Cao event.getText().add(text); 69404f06ea723b0445428d3f563334d653299a73a74Jin Cao event.setContentDescription(null); 69504f06ea723b0445428d3f563334d653299a73a74Jin Cao parent.requestSendAccessibilityEvent(this, event); 69604f06ea723b0445428d3f563334d653299a73a74Jin Cao } 69704f06ea723b0445428d3f563334d653299a73a74Jin Cao } 69804f06ea723b0445428d3f563334d653299a73a74Jin Cao } 69904f06ea723b0445428d3f563334d653299a73a74Jin Cao 70053e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin protected void scrollBottomIntoView() { 701093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp if (mScrollView != null && mShouldShrink) { 702fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao getLocationInWindow(mCoords); 703093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp // Desired position shows at least 1 line of chips below the action 704093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp // bar. We add excess padding to make sure this is always below other 705093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp // content. 70614efc5795c38120de239fad3d44b1b8b81045043Jin Cao final int height = getHeight(); 707fcaf9868ae2b549f1b95465e4e7877482403398dJin Cao final int currentPos = mCoords[1] + height; 708f6ddb7541feb5e415f9750fb403814dbbc9068beJin Cao mScrollView.getLocationInWindow(mCoords); 709f6ddb7541feb5e415f9750fb403814dbbc9068beJin Cao final int desiredPos = mCoords[1] + height / getLineCount(); 710093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp if (currentPos > desiredPos) { 711093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp mScrollView.scrollBy(0, currentPos - desiredPos); 712093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp } 713093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp } 714093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp } 715093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp 71653e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin protected ScrollView getScrollView() { 7177a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy return mScrollView; 71853e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin } 71953e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin 720d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira @Override 721d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira public void performValidation() { 722d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira // Do nothing. Chips handles its own validation. 723d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira } 724d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira 72512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira private void shrink() { 7265c125afa54288595524c85182031421cbad08ac3Mindy Pereira if (mTokenizer == null) { 7275c125afa54288595524c85182031421cbad08ac3Mindy Pereira return; 7285c125afa54288595524c85182031421cbad08ac3Mindy Pereira } 7295e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1; 7305e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT 7315e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp && (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) { 73212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira clearSelectedChip(); 73312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } else { 734d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira if (getWidth() <= 0) { 735d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira mHandler.removeCallbacks(mDelayedShrink); 736654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy 737654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy if (getVisibility() == GONE) { 738654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // We aren't going to have a width any time soon, so defer 739654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // this until we're not GONE. 740654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy mRequiresShrinkWhenNotGone = true; 741654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy } else { 742654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // We don't have the width yet which means the view hasn't been drawn yet 743654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // and there is no reason to attempt to commit chips yet. 744654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // This focus lost must be the result of an orientation change 745654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // or an initial rendering. 746654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy // Re-post the shrink for later. 747654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy mHandler.post(mDelayedShrink); 748654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy } 749d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira return; 750d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira } 751e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira // Reset any pending chips as they would have been handled 752e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira // when the field lost focus. 753e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira if (mPendingChipsCount > 0) { 7542cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira postHandlePendingChips(); 755e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira } else { 756e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira Editable editable = getText(); 757e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira int end = getSelectionEnd(); 758e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 759194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = 760194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy getSpannable().getSpans(start, end, DrawableRecipientChip.class); 761e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira if ((chips == null || chips.length == 0)) { 762d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira Editable text = getText(); 763d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 764d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira // This token was already tokenized, so skip past the ending token. 765448e90b97a4df8102d6e1d2039274d9ea188dff9Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 7664f82d888c680a61b95373740ce68bfb48a242617mindyp whatEnd = movePastTerminators(whatEnd); 767d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira } 768e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira // In the middle of chip; treat this as an edit 769e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira // and commit the whole token. 770d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira int selEnd = getSelectionEnd(); 7716337231a660b717cd6f5c40d524f4aabfcc865b0Mindy Pereira if (whatEnd != selEnd) { 772e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira handleEdit(start, whatEnd); 773e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira } else { 774e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira commitChip(start, end, editable); 775e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira } 776e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira } 777a74f40dc1073117349d1d39b7c8396a39d24f57fMindy Pereira } 778e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira mHandler.post(mAddTextWatcher); 77912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 7803bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira createMoreChip(); 78112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 78212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 78312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira private void expand() { 784374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp if (mShouldShrink) { 785374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp setMaxLines(Integer.MAX_VALUE); 786374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp } 78712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira removeMoreChip(); 78812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira setCursorVisible(true); 78912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira Editable text = getText(); 79012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 7911852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // If there are any temporary chips, try replacing them now that the user 7921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // has expanded the field. 7931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 7941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira new RecipientReplacementTask().execute(); 7951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira mTemporaryRecipients = null; 7961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 79712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 79812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 7992bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 8006e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira paint.setTextSize(mChipFontSize); 80102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 80202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 80302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira } 80402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 80502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira TextUtils.TruncateAt.END); 8062bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira } 807cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 808b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /** 809b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * Creates a bitmap of the given contact on a selected chip. 810b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * 811b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * @param contact The recipient entry to pull data from. 812b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * @param paint The paint to use to draw the bitmap. 813b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin */ 814e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private ChipBitmapContainer createChipBitmap(RecipientEntry contact, TextPaint paint) { 815cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang paint.setColor(getDefaultChipTextColor(contact)); 81633515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint, 81733515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao getChipBackground(contact), getDefaultChipBackgroundColor(contact)); 81835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 81935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao if (bitmapContainer.loadIcon) { 820809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao loadAvatarIcon(contact, bitmapContainer); 82135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao } 822e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return bitmapContainer; 823b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 824b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 82535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao private ChipBitmapContainer createChipBitmap(RecipientEntry contact, TextPaint paint, 82633515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao Drawable overrideBackgroundDrawable, int backgroundColor) { 82735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final ChipBitmapContainer result = new ChipBitmapContainer(); 82835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 82984f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker Drawable indicatorIcon = null; 83084f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker int indicatorPadding = 0; 831f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic if (contact.getIndicatorIconId() != 0) { 832f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic indicatorIcon = getContext().getDrawable(contact.getIndicatorIconId()); 83384f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker indicatorIcon.setBounds(0, 0, 83484f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker indicatorIcon.getIntrinsicWidth(), indicatorIcon.getIntrinsicHeight()); 83584f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker indicatorPadding = indicatorIcon.getBounds().width() + mChipTextEndPadding; 83684f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker } 83784f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker 838b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin Rect backgroundPadding = new Rect(); 83933515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao if (overrideBackgroundDrawable != null) { 84033515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao overrideBackgroundDrawable.getPadding(backgroundPadding); 84133515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao } 842b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 843cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 844cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 845cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // on the sides. 8466e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira int height = (int) mChipHeight; 847b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // Since the icon is a square, it's width is equal to the maximum height it can be inside 848a233af88e381e4a8caf917176974aeb0aa259c19Sandeep Siddhartha // the chip. Don't include iconWidth for invalid contacts and when not displaying photos. 849a233af88e381e4a8caf917176974aeb0aa259c19Sandeep Siddhartha boolean displayIcon = contact.isValid() && contact.shouldDisplayIcon(); 850a233af88e381e4a8caf917176974aeb0aa259c19Sandeep Siddhartha int iconWidth = displayIcon ? 85133515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao height - backgroundPadding.top - backgroundPadding.bottom : 0; 852e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 853e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final boolean shouldDisplayWarningIcon = mUntrustedAddresses.contains( 854e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam contact.getDestination()); 855e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float warningIconWidth = shouldDisplayWarningIcon ? mWarningIconHeight : 0; 856e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float warningIconTopMargin = (mChipHeight - mWarningIconHeight) / 2f; 857e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float warningIconEndMargin = shouldDisplayWarningIcon ? mChipTextEndPadding : 0; 858e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 8596ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira float[] widths = new float[1]; 8606ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira paint.getTextWidths(" ", widths); 861aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 862e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam calculateAvailableWidth() 863e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - iconWidth 864e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - warningIconWidth 865e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - warningIconEndMargin 866e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - widths[0] 867e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - backgroundPadding.left 868e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - backgroundPadding.right 869e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - indicatorPadding); 870b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length()); 871b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 87233515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao // Chip start padding is the same as the end padding if there is no contact image. 873a233af88e381e4a8caf917176974aeb0aa259c19Sandeep Siddhartha final int startPadding = displayIcon ? mChipTextStartPadding : mChipTextEndPadding; 8749d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 8759d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira // tap a chip without difficulty. 876e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam int width = Math.max(iconWidth * 2, 877e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam textWidth 878e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + startPadding 879e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + mChipTextEndPadding 880e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + iconWidth 881e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + (int) warningIconWidth 882e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + (int) warningIconEndMargin 883e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + backgroundPadding.left 884e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + backgroundPadding.right 885e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + indicatorPadding); 886cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 887cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Create the background of the chip. 88835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao result.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 88935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final Canvas canvas = new Canvas(result.bitmap); 8906b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira 89133515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao // Check if the background drawable is set via attr 89233515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao if (overrideBackgroundDrawable != null) { 89333515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao overrideBackgroundDrawable.setBounds(0, 0, width, height); 89433515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao overrideBackgroundDrawable.draw(canvas); 89533515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao } else { 89633515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao // Draw the default chip background 8976c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint.reset(); 8986c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint.setColor(backgroundColor); 89933515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao final float radius = height / 2; 90033515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao canvas.drawRoundRect(new RectF(0, 0, width, height), radius, radius, 9016c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint); 90233515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao } 90333515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao 904b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // Draw the text vertically aligned 905c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein int textX = shouldPositionAvatarOnRight() ? 906e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mChipTextEndPadding 907e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + backgroundPadding.left 908e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + indicatorPadding 909e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + (int) warningIconWidth 910e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam + (int) warningIconEndMargin : 911e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam width 912e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - backgroundPadding.right 913e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - mChipTextEndPadding 914e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - textWidth 915e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - indicatorPadding 916e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - (int) warningIconWidth 917e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam - (int) warningIconEndMargin; 918b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), 91915259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy textX, getTextYOffset(height), paint); 92035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 92184f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker if (indicatorIcon != null) { 92284f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker int indicatorX = shouldPositionAvatarOnRight() 92384f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker ? backgroundPadding.left + mChipTextEndPadding 92484f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker : width - backgroundPadding.right - indicatorIcon.getBounds().width() 92584f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker - mChipTextEndPadding; 92684f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker int indicatorY = height / 2 - indicatorIcon.getBounds().height() / 2; 92784f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker indicatorIcon.getBounds().offsetTo(indicatorX, indicatorY); 92884f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker indicatorIcon.draw(canvas); 92984f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker } 93084f848c6601f1759689c5318e8d01924ce2f0b2bBraden Walker 93135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao // Set the variables that are needed to draw the icon bitmap once it's loaded 932e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final int iconX = shouldPositionAvatarOnRight() ? 933e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam width - backgroundPadding.right - iconWidth : 93435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao backgroundPadding.left; 93535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao result.left = iconX; 93635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao result.top = backgroundPadding.top; 93735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao result.right = iconX + iconWidth; 93835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao result.bottom = height - backgroundPadding.bottom; 939a233af88e381e4a8caf917176974aeb0aa259c19Sandeep Siddhartha result.loadIcon = displayIcon; 94035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 941e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // Set the variables needed to draw the warning icon bitmap once it's loaded. 942e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float warningIconX = shouldPositionAvatarOnRight() ? 943e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam backgroundPadding.left + warningIconEndMargin : 944e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam width - backgroundPadding.right - warningIconWidth - warningIconEndMargin; 945e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float warningIconY = warningIconTopMargin; 946e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam result.warningIconLeft = warningIconX; 947e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam result.warningIconTop = warningIconY; 948e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam result.warningIconRight = warningIconX + warningIconWidth; 949e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam result.warningIconBottom = warningIconY + mWarningIconHeight; 950e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 95135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao return result; 95235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao } 95335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 95435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao /** 95535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao * Helper function that draws the loaded icon bitmap into the chips bitmap 95635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao */ 957809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao private void drawIcon(ChipBitmapContainer bitMapResult, Bitmap icon) { 958e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (icon == null) { 959e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return; 960e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 96135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final Canvas canvas = new Canvas(bitMapResult.bitmap); 96235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight()); 96335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final RectF dst = new RectF(bitMapResult.left, bitMapResult.top, bitMapResult.right, 96435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao bitMapResult.bottom); 965e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam drawCircularIconOnCanvas(icon, canvas, src, dst); 966e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 967e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 968e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam /** 969e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * Draws the warning icon onto the chip's bitmap and returns the rectangle it drew on. 970e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam */ 971e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private RectF drawWarningIcon(ChipBitmapContainer bitMapResult) { 972e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (mWarningIcon == null) { 973e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return new RectF(0, 0, 0, 0); 974e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 975e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final Canvas canvas = new Canvas(bitMapResult.bitmap); 976e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final RectF src = new RectF(0, 0, mWarningIcon.getWidth(), mWarningIcon.getHeight()); 977e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final RectF dst = new RectF(bitMapResult.warningIconLeft, bitMapResult.warningIconTop, 978e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam bitMapResult.warningIconRight, bitMapResult.warningIconBottom); 979e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam drawRectanglularIconOnCanvas(mWarningIcon, canvas, src, dst); 980e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return dst; 9812bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira } 982cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 983aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira /** 984c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein * Returns true if the avatar should be positioned at the right edge of the chip. 985c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein * Takes into account both the set avatar position (start or end) as well as whether 986c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein * the layout direction is LTR or RTL. 987c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein */ 988c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein private boolean shouldPositionAvatarOnRight() { 989ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && 990ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao getLayoutDirection() == LAYOUT_DIRECTION_RTL; 991c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein final boolean assignedPosition = mAvatarPosition == AVATAR_POSITION_END; 992c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein // If in Rtl mode, the position should be flipped. 993c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein return isRtl ? !assignedPosition : assignedPosition; 994c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein } 995c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein 996c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein /** 997b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to 998b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * draw an icon for this recipient. 999b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin */ 100035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao private void loadAvatarIcon(final RecipientEntry contact, 1001809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao final ChipBitmapContainer bitmapContainer) { 1002b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // Don't draw photos for recipients that have been typed in OR generated on the fly. 1003b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin long contactId = contact.getContactId(); 1004b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin boolean drawPhotos = isPhoneQuery() ? 1005b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin contactId != RecipientEntry.INVALID_CONTACT 1006b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin : (contactId != RecipientEntry.INVALID_CONTACT 1007bf84bd8f061da4f5a494d20192eae53db9867785Jin Cao && contactId != RecipientEntry.GENERATED_CONTACT); 1008b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 1009b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin if (drawPhotos) { 101035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final byte[] origPhotoBytes = contact.getPhotoBytes(); 1011b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // There may not be a photo yet if anything but the first contact address 1012b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // was selected. 101382d388545ba0b52854801c946ed2f62e05e7b563Jin Cao if (origPhotoBytes == null) { 1014b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin // TODO: cache this in the recipient entry? 101535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao getAdapter().fetchPhoto(contact, new PhotoManager.PhotoManagerCallback() { 101652e4814f46230835ed48d7561de8359b79b978cdJin Cao @Override 101750429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein public void onPhotoBytesPopulated() { 101850429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein // Call through to the async version which will ensure 101950429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein // proper threading. 102050429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein onPhotoBytesAsynchronouslyPopulated(); 102150429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein } 102250429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein 102350429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein @Override 102452e4814f46230835ed48d7561de8359b79b978cdJin Cao public void onPhotoBytesAsynchronouslyPopulated() { 102552e4814f46230835ed48d7561de8359b79b978cdJin Cao final byte[] loadedPhotoBytes = contact.getPhotoBytes(); 102652e4814f46230835ed48d7561de8359b79b978cdJin Cao final Bitmap icon = BitmapFactory.decodeByteArray(loadedPhotoBytes, 0, 102752e4814f46230835ed48d7561de8359b79b978cdJin Cao loadedPhotoBytes.length); 102852e4814f46230835ed48d7561de8359b79b978cdJin Cao tryDrawAndInvalidate(icon); 102952e4814f46230835ed48d7561de8359b79b978cdJin Cao } 10300efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao 103152e4814f46230835ed48d7561de8359b79b978cdJin Cao @Override 103252e4814f46230835ed48d7561de8359b79b978cdJin Cao public void onPhotoBytesAsyncLoadFailed() { 103352e4814f46230835ed48d7561de8359b79b978cdJin Cao // TODO: can the scaled down default photo be cached? 103452e4814f46230835ed48d7561de8359b79b978cdJin Cao tryDrawAndInvalidate(mDefaultContactPhoto); 103552e4814f46230835ed48d7561de8359b79b978cdJin Cao } 103652e4814f46230835ed48d7561de8359b79b978cdJin Cao 103752e4814f46230835ed48d7561de8359b79b978cdJin Cao private void tryDrawAndInvalidate(Bitmap icon) { 1038809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao drawIcon(bitmapContainer, icon); 103952e4814f46230835ed48d7561de8359b79b978cdJin Cao // The caller might originated from a background task. However, if the 104052e4814f46230835ed48d7561de8359b79b978cdJin Cao // background task has already completed, the view might be already drawn 104152e4814f46230835ed48d7561de8359b79b978cdJin Cao // on the UI but the callback would happen on the background thread. 104252e4814f46230835ed48d7561de8359b79b978cdJin Cao // So if we are on a background thread, post an invalidate call to the UI. 104352e4814f46230835ed48d7561de8359b79b978cdJin Cao if (Looper.myLooper() == Looper.getMainLooper()) { 104452e4814f46230835ed48d7561de8359b79b978cdJin Cao // The view might not redraw itself since it's loaded asynchronously 10450efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao invalidate(); 104652e4814f46230835ed48d7561de8359b79b978cdJin Cao } else { 104752e4814f46230835ed48d7561de8359b79b978cdJin Cao post(new Runnable() { 104852e4814f46230835ed48d7561de8359b79b978cdJin Cao @Override 104952e4814f46230835ed48d7561de8359b79b978cdJin Cao public void run() { 105052e4814f46230835ed48d7561de8359b79b978cdJin Cao invalidate(); 105152e4814f46230835ed48d7561de8359b79b978cdJin Cao } 105252e4814f46230835ed48d7561de8359b79b978cdJin Cao }); 10530efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao } 105452e4814f46230835ed48d7561de8359b79b978cdJin Cao } 105535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao }); 1056b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } else { 105735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao final Bitmap icon = BitmapFactory.decodeByteArray(origPhotoBytes, 0, 105835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao origPhotoBytes.length); 1059809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao drawIcon(bitmapContainer, icon); 1060b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 1061b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 1062b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 1063b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 1064b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /** 1065aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira * Get the background drawable for a RecipientChip. 1066aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira */ 1067aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Visible for testing. 1068f30a42800318f6790d55421f8f6980eb38db4d3cmindyp /* package */Drawable getChipBackground(RecipientEntry contact) { 1069f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return contact.isValid() ? mChipBackground : mInvalidChipBackground; 1070aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } 1071aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira 1072cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang private int getDefaultChipTextColor(RecipientEntry contact) { 1073cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang return contact.isValid() ? mUnselectedChipTextColor : 1074cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang getResources().getColor(android.R.color.black); 1075cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang } 1076cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang 107733515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao private int getDefaultChipBackgroundColor(RecipientEntry contact) { 1078cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang return contact.isValid() ? mUnselectedChipBackgroundColor : 1079cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang getResources().getColor(R.color.chip_background_invalid); 108033515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao } 108133515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao 1082b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /** 1083b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * Given a height, returns a Y offset that will draw the text in the middle of the height. 1084b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin */ 108515259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy protected float getTextYOffset(int height) { 108615259d5bab45b9e561d33e5d1d23df3a1476a430Scott Kennedy return height - ((height - mTextHeight) / 2); 10871d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira } 10881d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira 1089b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /** 1090b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination 1091b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin * rectangle of the canvas. 1092e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * 1093e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * <p>The icon is drawn as a circle. 1094b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin */ 1095e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam protected void drawCircularIconOnCanvas(Bitmap icon, Canvas canvas, RectF src, RectF dst) { 1096e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam setWorkPaintForIcon(icon, src, dst); 1097e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2f, mWorkPaint); 1098e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1099e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float borderWidth = 1f; 1100e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam setWorkPaintForBorder(borderWidth); 1101e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2f - borderWidth / 2, 1102e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWorkPaint); 1103e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1104e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWorkPaint.reset(); 1105e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1106e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1107e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam /** 1108e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination 1109e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * rectangle of the canvas. 1110e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * 1111e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * <p>The icon is drawn as a rectangle. 1112e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam */ 1113e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private void drawRectanglularIconOnCanvas(Bitmap icon, Canvas canvas, RectF src, RectF dst) { 1114e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam setWorkPaintForIcon(icon, src, dst); 1115e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam canvas.drawRect(dst, mWorkPaint); 1116e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1117e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final float borderWidth = 1f; 1118e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam setWorkPaintForBorder(borderWidth); 1119e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam canvas.drawRect(dst, mWorkPaint); 1120e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1121e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mWorkPaint.reset(); 1122e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1123e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam 1124e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam /** 1125e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * Sets WorkPaint for drawing the icon from src onto dst. 1126e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam */ 1127e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private void setWorkPaintForIcon(Bitmap icon, RectF src, RectF dst) { 1128b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy final Matrix matrix = new Matrix(); 1129b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy 1130b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy // Draw bitmap through shader first. 1131b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy final BitmapShader shader = new BitmapShader(icon, TileMode.CLAMP, TileMode.CLAMP); 1132b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy matrix.reset(); 1133b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy 1134b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy // Fit bitmap to bounds. 1135b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 1136b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy 1137b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy shader.setLocalMatrix(matrix); 1138809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.reset(); 1139809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setShader(shader); 1140809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setAntiAlias(true); 1141809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setFilterBitmap(true); 1142809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setDither(true); 1143e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1144b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy 1145e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam /** 1146e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam * Sets WorkPaint for drawing the icon border with the given width. 1147e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam */ 1148e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam private void setWorkPaintForBorder(float borderWidth) { 1149809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.reset(); 1150809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setColor(Color.TRANSPARENT); 1151809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setStyle(Style.STROKE); 1152809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setStrokeWidth(borderWidth); 1153809c595762c1d5adfe93d61eafa5fc9aa48c7de3Jin Cao mWorkPaint.setAntiAlias(true); 1154b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 1155b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 1156ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao private DrawableRecipientChip constructChipSpan(RecipientEntry contact) { 11572bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira TextPaint paint = getPaint(); 11582bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira float defaultSize = paint.getTextSize(); 1159e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira int defaultColor = paint.getColor(); 11602bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira 1161e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint); 1162e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final Rect warningIconBounds = new Rect(0, 0, 0, 0); 1163e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (mUntrustedAddresses.contains(contact.getDestination())) { 1164e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam drawWarningIcon(bitmapContainer).round(warningIconBounds); 1165e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1166e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final Bitmap tmpBitmap = bitmapContainer.bitmap; 1167cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1168cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Pass the full text, un-ellipsized, to the chip. 1169e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final int iconWidth = tmpBitmap != null ? tmpBitmap.getWidth() : 0; 1170e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final int iconHeight = tmpBitmap != null ? tmpBitmap.getHeight() : 0; 1171cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 1172e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam result.setBounds(0, 0, iconWidth, iconHeight); 11736c2a016fb00482339dc66b93822042f9f9e2424fJin Cao VisibleRecipientChip recipientChip = 11746c2a016fb00482339dc66b93822042f9f9e2424fJin Cao new VisibleRecipientChip(result, contact); 11756c2a016fb00482339dc66b93822042f9f9e2424fJin Cao recipientChip.setExtraMargin(mLineSpacingExtra); 1176cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Return text to the original size. 1177cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira paint.setTextSize(defaultSize); 1178e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira paint.setColor(defaultColor); 1179e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // Put warning icon dimensions info in the chip 1180e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam recipientChip.setWarningIconBounds(warningIconBounds); 1181cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return recipientChip; 11829159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira } 11839159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 1184a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 11856fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao * Calculate the offset from bottom of the EditText to top of the provided line. 11866fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao */ 11876fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao private int calculateOffsetFromBottomToTop(int line) { 11886fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao return -(int) ((mChipHeight + (2 * mLineSpacingExtra)) * (Math 11896fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao .abs(getLineCount() - line)) + getPaddingBottom()); 11906fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao } 11916fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao 11926fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao /** 1193a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 1194a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * account the width of the EditTextView, any view padding, and padding 1195a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * that will be added to the chip. 1196a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 1197f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy private float calculateAvailableWidth() { 119858d541de27090b4bd55746368b3b3b1486a7c094Jin Cao return getWidth() - getPaddingLeft() - getPaddingRight() - mChipTextStartPadding 119958d541de27090b4bd55746368b3b3b1486a7c094Jin Cao - mChipTextEndPadding; 12009159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira } 12019159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 120252c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira 120322faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira private void setChipDimensions(Context context, AttributeSet attrs) { 12045da0234c9a7108d3386039816c7469753b79c307Mindy Pereira TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecipientEditTextView, 0, 12055da0234c9a7108d3386039816c7469753b79c307Mindy Pereira 0); 120652c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira Resources r = getContext().getResources(); 12078c474ec629cca3cf4bf2c867f37513ae35e3ff72mindyp 120822faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira mChipBackground = a.getDrawable(R.styleable.RecipientEditTextView_chipBackground); 120933515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao mInvalidChipBackground = a 121033515e70c6dc7b488bc0aa62f6a009bec4bf8812Jin Cao .getDrawable(R.styleable.RecipientEditTextView_invalidChipBackground); 12115da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mChipDelete = a.getDrawable(R.styleable.RecipientEditTextView_chipDelete); 12125da0234c9a7108d3386039816c7469753b79c307Mindy Pereira if (mChipDelete == null) { 1213a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao mChipDelete = r.getDrawable(R.drawable.ic_cancel_wht_24dp); 12145da0234c9a7108d3386039816c7469753b79c307Mindy Pereira } 121558d541de27090b4bd55746368b3b3b1486a7c094Jin Cao mChipTextStartPadding = mChipTextEndPadding 121658d541de27090b4bd55746368b3b3b1486a7c094Jin Cao = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipPadding, -1); 121758d541de27090b4bd55746368b3b3b1486a7c094Jin Cao if (mChipTextStartPadding == -1) { 121858d541de27090b4bd55746368b3b3b1486a7c094Jin Cao mChipTextStartPadding = mChipTextEndPadding = 121958d541de27090b4bd55746368b3b3b1486a7c094Jin Cao (int) r.getDimension(R.dimen.chip_padding); 122058d541de27090b4bd55746368b3b3b1486a7c094Jin Cao } 122158d541de27090b4bd55746368b3b3b1486a7c094Jin Cao // xml-overrides for each individual padding 122258d541de27090b4bd55746368b3b3b1486a7c094Jin Cao // TODO: add these to attr? 122358d541de27090b4bd55746368b3b3b1486a7c094Jin Cao int overridePadding = (int) r.getDimension(R.dimen.chip_padding_start); 122458d541de27090b4bd55746368b3b3b1486a7c094Jin Cao if (overridePadding >= 0) { 122558d541de27090b4bd55746368b3b3b1486a7c094Jin Cao mChipTextStartPadding = overridePadding; 122658d541de27090b4bd55746368b3b3b1486a7c094Jin Cao } 122758d541de27090b4bd55746368b3b3b1486a7c094Jin Cao overridePadding = (int) r.getDimension(R.dimen.chip_padding_end); 122858d541de27090b4bd55746368b3b3b1486a7c094Jin Cao if (overridePadding >= 0) { 122958d541de27090b4bd55746368b3b3b1486a7c094Jin Cao mChipTextEndPadding = overridePadding; 12305da0234c9a7108d3386039816c7469753b79c307Mindy Pereira } 12315da0234c9a7108d3386039816c7469753b79c307Mindy Pereira 12325da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture); 12335da0234c9a7108d3386039816c7469753b79c307Mindy Pereira 123452c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null); 12355da0234c9a7108d3386039816c7469753b79c307Mindy Pereira 12365da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mChipHeight = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipHeight, -1); 12375da0234c9a7108d3386039816c7469753b79c307Mindy Pereira if (mChipHeight == -1) { 12385da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mChipHeight = r.getDimension(R.dimen.chip_height); 12395da0234c9a7108d3386039816c7469753b79c307Mindy Pereira } 12405da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mChipFontSize = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipFontSize, -1); 12415da0234c9a7108d3386039816c7469753b79c307Mindy Pereira if (mChipFontSize == -1) { 12425da0234c9a7108d3386039816c7469753b79c307Mindy Pereira mChipFontSize = r.getDimension(R.dimen.chip_text_size); 12435da0234c9a7108d3386039816c7469753b79c307Mindy Pereira } 1244b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy mAvatarPosition = 1245b232e5c33e1e2879c7bc8e96a1b9c41c4fa41244Scott Kennedy a.getInt(R.styleable.RecipientEditTextView_avatarPosition, AVATAR_POSITION_START); 1246b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_disableDelete, false); 1247b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 1248f30a42800318f6790d55421f8f6980eb38db4d3cmindyp mMaxLines = r.getInteger(R.integer.chips_max_lines); 12496c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mLineSpacingExtra = r.getDimensionPixelOffset(R.dimen.line_spacing_extra); 1250b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 1251cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang mUnselectedChipTextColor = a.getColor( 1252cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang R.styleable.RecipientEditTextView_unselectedChipTextColor, 1253cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang r.getColor(android.R.color.black)); 1254cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang 1255cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang mUnselectedChipBackgroundColor = a.getColor( 1256cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang R.styleable.RecipientEditTextView_unselectedChipBackgroundColor, 1257cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang r.getColor(R.color.chip_background)); 1258cb11fced23f3a08ad68ea61719d7f0fdeb0f7279Kaikai Wang 1259ef79800ff65490ec91ce77f55c4ade142d1cc4b2Mindy Pereira a.recycle(); 126052c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira } 126152c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira 1262d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira // Visible for testing. 1263d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira /* package */ void setMoreItem(TextView moreItem) { 1264d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira mMoreItem = moreItem; 1265d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira } 1266d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira 126797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira 126897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira // Visible for testing. 126997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 127097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira mChipBackground = chipBackground; 127197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira } 127297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira 127397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira // Visible for testing. 127497cb25912dab282cf732757f68b0405ed005f00bMindy Pereira /* package */ void setChipHeight(int height) { 127597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira mChipHeight = height; 127697cb25912dab282cf732757f68b0405ed005f00bMindy Pereira } 127797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira 1278b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin public float getChipHeight() { 1279b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin return mChipHeight; 1280b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin } 1281b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin 12821163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz /** Returns whether view is in no-chip or chip mode. */ 12831163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz public boolean isNoChipMode() { 12841163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz return mNoChipMode; 12851163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz } 12861163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz 1287076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira /** 1288076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira * Set whether to shrink the recipients field such that at most 1289076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira * one line of recipients chips are shown when the field loses 1290076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira * focus. By default, the number of displayed recipients will be 1291076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira * limited and a "more" chip will be shown when focus is lost. 1292076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira * @param shrink 1293076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira */ 1294076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 1295076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira mShouldShrink = shrink; 1296076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira } 1297076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira 1298cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 129902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 130002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 1301db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira if (width != 0 && height != 0) { 1302db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira if (mPendingChipsCount > 0) { 1303db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira postHandlePendingChips(); 1304db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } else { 1305db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira checkChipWidths(); 1306db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } 13078005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira } 13081852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // Try to find the scroll view parent, if it exists. 1309aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira if (mScrollView == null && !mTriedGettingScrollView) { 1310c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira ViewParent parent = getParent(); 1311c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 1312c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira parent = parent.getParent(); 1313c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira } 1314c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira if (parent != null) { 1315c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira mScrollView = (ScrollView) parent; 1316c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira } 1317aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira mTriedGettingScrollView = true; 1318c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira } 131902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira } 132002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira 13212cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira private void postHandlePendingChips() { 13222cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 13232cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mHandler.post(mHandlePendingChips); 13242cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 13252cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira 1326db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira private void checkChipWidths() { 1327db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira // Check the widths of the associated chips. 1328194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = getSortedRecipients(); 1329db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira if (chips != null) { 1330db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira Rect bounds; 1331194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (DrawableRecipientChip chip : chips) { 1332f30a42800318f6790d55421f8f6980eb38db4d3cmindyp bounds = chip.getBounds(); 1333c61c13ffde5d4224ee861f7e7fe3ea0900128a7cKevin Lin if (getWidth() > 0 && bounds.right - bounds.left > 1334c61c13ffde5d4224ee861f7e7fe3ea0900128a7cKevin Lin getWidth() - getPaddingLeft() - getPaddingRight()) { 1335db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira // Need to redraw that chip. 1336db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira replaceChip(chip, chip.getEntry()); 1337db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } 1338db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } 13392cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 1340db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } 1341db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira 1342f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Visible for testing. 1343f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira /*package*/ void handlePendingChips() { 1344f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (getViewWidth() <= 0) { 13458005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira // The widget has not been sized yet. 13468005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira // This will be called as a result of onSizeChanged 13478005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira // at a later point. 13488005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira return; 13498005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira } 1350db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira if (mPendingChipsCount <= 0) { 1351db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira return; 1352db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira } 1353db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira 13542cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira synchronized (mPendingChips) { 13552cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira Editable editable = getText(); 13562cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira // Tokenize! 1357f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (mPendingChipsCount <= MAX_CHIPS_PARSED) { 1358f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 1359f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira String current = mPendingChips.get(i); 1360f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int tokenStart = editable.toString().indexOf(current); 1361488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp // Always leave a space at the end between tokens. 1362488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp int tokenEnd = tokenStart + current.length() - 1; 1363f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (tokenStart >= 0) { 1364f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // When we have a valid token, include it with the token 1365f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // to the left. 1366f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (tokenEnd < editable.length() - 2 1367f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 1368f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira tokenEnd++; 1369f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 1370f30a42800318f6790d55421f8f6980eb38db4d3cmindyp createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT 1371f30a42800318f6790d55421f8f6980eb38db4d3cmindyp || !mShouldShrink); 13722cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 1373f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira mPendingChipsCount--; 13743bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 1375f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira sanitizeEnd(); 1376f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } else { 13771163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz mNoChipMode = true; 13783bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 1379f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 1380dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 13812cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 13822cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 13832cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira new RecipientReplacementTask().execute(); 13842cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mTemporaryRecipients = null; 13852cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } else { 13862cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira // Create the "more" chip 13872cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 1388194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>( 13892cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 13905e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp if (mTemporaryRecipients.size() > CHIP_LIMIT) { 1391194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy mTemporaryRecipients = new ArrayList<DrawableRecipientChip>( 13925e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp mTemporaryRecipients.subList(CHIP_LIMIT, 13935e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp mTemporaryRecipients.size())); 13945e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } else { 13955e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp mTemporaryRecipients = null; 13965e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } 13972cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira createMoreChip(); 13982cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } 13992cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira } else { 14002cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira // There are too many recipients to look up, so just fall back 1401dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira // to showing addresses for all of them. 14022cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mTemporaryRecipients = null; 14031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira createMoreChip(); 14041852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 14052cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mPendingChipsCount = 0; 14062cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira mPendingChips.clear(); 14073bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14083bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14093bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 1410f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Visible for testing. 1411f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira /*package*/ int getViewWidth() { 1412f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return getWidth(); 1413f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 1414f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 14153bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira /** 14163bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira * Remove any characters after the last valid chip. 14173bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira */ 1418aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Visible for testing. 1419aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira /*package*/ void sanitizeEnd() { 14203c42baf5b81810bce77e776963f20c960865e85bMindy Pereira // Don't sanitize while we are waiting for pending chips to complete. 14213c42baf5b81810bce77e776963f20c960865e85bMindy Pereira if (mPendingChipsCount > 0) { 14223c42baf5b81810bce77e776963f20c960865e85bMindy Pereira return; 14233c42baf5b81810bce77e776963f20c960865e85bMindy Pereira } 14243bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira // Find the last chip; eliminate any commit characters after it. 1425194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = getSortedRecipients(); 1426f30a42800318f6790d55421f8f6980eb38db4d3cmindyp Spannable spannable = getSpannable(); 14273bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira if (chips != null && chips.length > 0) { 14283bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira int end; 1429dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira mMoreChip = getMoreChip(); 14303bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira if (mMoreChip != null) { 1431f30a42800318f6790d55421f8f6980eb38db4d3cmindyp end = spannable.getSpanEnd(mMoreChip); 14323bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } else { 1433f30a42800318f6790d55421f8f6980eb38db4d3cmindyp end = getSpannable().getSpanEnd(getLastChip()); 14343bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14353bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira Editable editable = getText(); 14363bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira int length = editable.length(); 14373bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira if (length > end) { 14383bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira // See what characters occur after that and eliminate them. 14393bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 14403bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 14413bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira + editable); 14423bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14433bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira editable.delete(end + 1, length); 14443bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14453bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14463bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14473bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 14483bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira /** 14493bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 14503bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 14513bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira */ 145220c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy // VisibleForTesting 145320c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy void createReplacementChip(int tokenStart, int tokenEnd, Editable editable, 1454f30a42800318f6790d55421f8f6980eb38db4d3cmindyp boolean visible) { 145532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 145632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira // There is already a chip present at this location. 145732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira // Don't recreate it. 145832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira return; 145932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira } 14607ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 146120c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy final String trimmedToken = token.trim(); 146220c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy int commitCharIndex = trimmedToken.lastIndexOf(COMMIT_CHAR_COMMA); 146341137c249b5fe547a3631d9c6db57ff980022affTom Taylor if (commitCharIndex != -1 && commitCharIndex == trimmedToken.length() - 1) { 146420c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy token = trimmedToken.substring(0, trimmedToken.length() - 1); 14653bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14663bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 1467ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira if (entry != null) { 1468194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip chip = null; 1469ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira try { 14701163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode) { 1471ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao chip = visible ? constructChipSpan(entry) : new InvisibleRecipientChip(entry); 1472f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 1473ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } catch (NullPointerException e) { 1474ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira Log.e(TAG, e.getMessage(), e); 1475ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } 1476cb76b4d2bcb7b72b9505a620ca71f753506e48b9mindyp editable.setSpan(chip, tokenStart, tokenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1477ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira // Add this chip to the list of entries "to replace" 1478ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira if (chip != null) { 1479dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira if (mTemporaryRecipients == null) { 1480194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(); 1481dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira } 14820613ff859a6b44685af62821eac369597cf69b26Scott Kennedy chip.setOriginalText(token); 1483ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira mTemporaryRecipients.add(chip); 1484ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } 14851852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 14863bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 14873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 1488983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy // VisibleForTesting 1489983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy RecipientEntry createTokenizedEntry(final String token) { 1490ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira if (TextUtils.isEmpty(token)) { 1491ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira return null; 1492ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } 149323f4fd2e3581f29e164d4b62a9095ab1e492c009Michael Berberet if (isPhoneQuery() && PhoneUtil.isPhoneNumber(token)) { 14940ba9133c904b8c35af8209a54604331cd671bc1fTom Taylor return RecipientEntry.constructFakePhoneEntry(token, true); 14955e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } 14963bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 1497f30a42800318f6790d55421f8f6980eb38db4d3cmindyp boolean isValid = isValid(token); 1498f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (isValid && tokens != null && tokens.length > 0) { 1499ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira // If we can get a name from tokenizing, then generate an entry from 1500ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira // this. 1501ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao String display = tokens[0].getName(); 150201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira if (!TextUtils.isEmpty(display)) { 1503f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return RecipientEntry.constructGeneratedEntry(display, tokens[0].getAddress(), 1504f30a42800318f6790d55421f8f6980eb38db4d3cmindyp isValid); 1505401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira } else { 1506401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira display = tokens[0].getAddress(); 1507401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira if (!TextUtils.isEmpty(display)) { 1508f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return RecipientEntry.constructFakeEntry(display, isValid); 1509401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira } 151001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 151101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 15124e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira // Unable to validate the token or to create a valid token from it. 15134e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira // Just create a chip the user can edit. 1514dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira String validatedToken = null; 1515f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (mValidator != null && !isValid) { 15164e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira // Try fixing up the entry using the validator. 1517dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira validatedToken = mValidator.fixText(token).toString(); 1518dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 1519dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira if (validatedToken.contains(token)) { 1520f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // protect against the case of a validator with a null 1521f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // domain, 1522dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira // which doesn't add a domain to the token 1523dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 1524dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira if (tokenized.length > 0) { 1525dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira validatedToken = tokenized[0].getAddress(); 1526f30a42800318f6790d55421f8f6980eb38db4d3cmindyp isValid = true; 1527dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira } 1528dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira } else { 1529f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // We ran into a case where the token was invalid and 1530f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // removed 1531f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // by the validator. In this case, just use the original 1532f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // token 1533dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira // and let the user sort out the error chip. 1534dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira validatedToken = null; 1535f30a42800318f6790d55421f8f6980eb38db4d3cmindyp isValid = false; 1536184d3773dc096c7a6a83f7aeda042fa8346c2024Erik } 1537ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } 15384e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira } 1539ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira // Otherwise, fallback to just creating an editable email address chip. 1540f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return RecipientEntry.constructFakeEntry( 1541f30a42800318f6790d55421f8f6980eb38db4d3cmindyp !TextUtils.isEmpty(validatedToken) ? validatedToken : token, isValid); 154201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 154301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira 15444e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira private boolean isValid(String text) { 15454e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira return mValidator == null ? true : mValidator.isValid(text); 15464e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira } 15474e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira 1548f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy private static String tokenizeAddress(String destination) { 154901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 155001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira if (tokens != null && tokens.length > 0) { 155101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira return tokens[0].getAddress(); 15523bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 155301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira return destination; 15543bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 15553bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 155602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira @Override 1557cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 1558cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira mTokenizer = tokenizer; 1559cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira super.setTokenizer(mTokenizer); 1560cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1561cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1562a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira @Override 1563a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira public void setValidator(Validator validator) { 1564a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira mValidator = validator; 1565a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira super.setValidator(validator); 1566a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira } 1567a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira 1568a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 1569a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 1570a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * we override onItemClickListener so we can get all the associated 1571a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * contact information including display text, address, and id. 1572a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 1573cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 1574cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira protected void replaceText(CharSequence text) { 1575cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return; 1576cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1577cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1578a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 1579a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * Dismiss any selected chips when the back key is pressed. 1580a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 1581cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 1582ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) { 158303cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK && mSelectedChip != null) { 15844c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira clearSelectedChip(); 158503cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira return true; 15864c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira } 15874c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira return super.onKeyPreIme(keyCode, event); 15884c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira } 15894c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira 1590a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 1591a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * Monitor key presses in this view to see if the user types 1592a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 1593a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * If the user has entered text that has contact matches and types 1594a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * a commit key, create a chip from the topmost matching contact. 1595a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * If the user has entered text that has no contact matches and types 1596a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * a commit key, then create a chip from the text they have entered. 1597a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 15984c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira @Override 1599ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { 1600cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira switch (keyCode) { 16010f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira case KeyEvent.KEYCODE_TAB: 16020f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira if (event.hasNoModifiers()) { 16030f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira if (mSelectedChip != null) { 16040f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira clearSelectedChip(); 16050f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira } else { 16060f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira commitDefault(); 16070f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira } 16080f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira } 160903cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira break; 1610cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1611cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return super.onKeyUp(keyCode, event); 1612cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1613cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 16140f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira private boolean focusNext() { 16150f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira View next = focusSearch(View.FOCUS_DOWN); 16160f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira if (next != null) { 16170f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira next.requestFocus(); 16180f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira return true; 16190f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira } 16200f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira return false; 16210f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira } 16220f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira 1623342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira /** 1624342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 1625e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy * default is the selected item (if one is selected), or the first item, in the popup 1626e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy * suggestions list. Otherwise, it is whatever the user had typed in. End represents where the 1627e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy * tokenizer should search for a token to turn into a chip. 1628342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira * @return If a chip was created from a real contact. 1629342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira */ 1630a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira private boolean commitDefault() { 163160d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira // If there is no tokenizer, don't try to commit. 163260d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira if (mTokenizer == null) { 163360d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira return false; 163460d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira } 163512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira Editable editable = getText(); 1636342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira int end = getSelectionEnd(); 163712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 16382b4ffc53a3b51631cb6aabf535986a9344ee6cbbMindy Pereira 163979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira if (shouldCreateChip(start, end)) { 164079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 164179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira // In the middle of chip; treat this as an edit 164279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira // and commit the whole token. 16434f82d888c680a61b95373740ce68bfb48a242617mindyp whatEnd = movePastTerminators(whatEnd); 164479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira if (whatEnd != getSelectionEnd()) { 164579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira handleEdit(start, whatEnd); 164679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira return true; 164712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 164879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira return commitChip(start, end , editable); 164979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 165079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira return false; 165179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 165279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira 165379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private void commitByCharacter() { 165460d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira // We can't possibly commit by character if we can't tokenize. 165560d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira if (mTokenizer == null) { 165660d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira return; 165760d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira } 165879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira Editable editable = getText(); 165979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira int end = getSelectionEnd(); 166079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 166179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira if (shouldCreateChip(start, end)) { 166279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira commitChip(start, end, editable); 166312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 1664ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira setSelection(getText().length()); 166579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 166612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 166779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private boolean commitChip(int start, int end, Editable editable) { 16684686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong int position = positionOfFirstEntryWithTypePerson(); 16694686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong if (position != -1 && enoughToFilter() 1670dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz && end == getSelectionEnd() && !isPhoneQuery() 1671dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz && !isValidEmailAddress(editable.toString().substring(start, end).trim())) { 1672e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy // let's choose the selected or first entry if only the input text is NOT an email 1673e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy // address so we won't try to replace the user's potentially correct but 1674e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy // new/unencountered email input 1675dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz final int selectedPosition = getListSelection(); 1676dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz if (selectedPosition == -1 || !isEntryAtPositionTypePerson(selectedPosition)) { 1677dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz // Nothing is selected or selected item is not type person; use the first item 1678dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz submitItemAtPosition(position); 1679dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz } else { 1680dbe88c3002c8a8d7f8cceaef988462502e4c557bEkin Oguz submitItemAtPosition(selectedPosition); 1681b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao } 168279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira dismissDropDown(); 168379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira return true; 168479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } else { 168579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 168620c9d620e79ae28994856541761a951074551518Mindy Pereira if (editable.length() > tokenEnd + 1) { 168720c9d620e79ae28994856541761a951074551518Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 168820c9d620e79ae28994856541761a951074551518Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 168920c9d620e79ae28994856541761a951074551518Mindy Pereira tokenEnd++; 169020c9d620e79ae28994856541761a951074551518Mindy Pereira } 1691d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira } 169262397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 169379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira clearComposingText(); 1694ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao if (text.length() > 0 && !text.equals(" ")) { 169501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1696ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira if (entry != null) { 1697ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1698ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao CharSequence chipText = createChip(entry); 1699ee6d83fe1da297b2f9af0fb221be376fdc816830Mindy Pereira if (chipText != null && start > -1 && end > -1) { 1700f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira editable.replace(start, end, chipText); 1701f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira } 1702ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira } 170320c9d620e79ae28994856541761a951074551518Mindy Pereira // Only dismiss the dropdown if it is related to the text we 170420c9d620e79ae28994856541761a951074551518Mindy Pereira // just committed. 170520c9d620e79ae28994856541761a951074551518Mindy Pereira // For paste, it may not be as there are possibly multiple 170620c9d620e79ae28994856541761a951074551518Mindy Pereira // tokens being added. 170720c9d620e79ae28994856541761a951074551518Mindy Pereira if (end == getSelectionEnd()) { 170820c9d620e79ae28994856541761a951074551518Mindy Pereira dismissDropDown(); 170920c9d620e79ae28994856541761a951074551518Mindy Pereira } 17105df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira sanitizeBetween(); 171112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira return true; 171205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira } 171305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira } 171412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira return false; 171505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira } 171605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira 17174686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong private int positionOfFirstEntryWithTypePerson() { 17184686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong ListAdapter adapter = getAdapter(); 17194686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong int itemCount = adapter != null ? adapter.getCount() : 0; 17204686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong for (int i = 0; i < itemCount; i++) { 17214686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong if (isEntryAtPositionTypePerson(i)) { 17224686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong return i; 17234686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong } 17244686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong } 17254686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong return -1; 17264686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong } 17274686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong 17284686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong private boolean isEntryAtPositionTypePerson(int position) { 17294686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong return getAdapter().getItem(position).getEntryType() == RecipientEntry.ENTRY_TYPE_PERSON; 17304686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong } 17314686ef5cf68cb1ea190f82a05b244d276d99d1bbJae Yoon Chong 173201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira // Visible for testing. 173301162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira /* package */ void sanitizeBetween() { 17343c42baf5b81810bce77e776963f20c960865e85bMindy Pereira // Don't sanitize while we are waiting for content to chipify. 17353c42baf5b81810bce77e776963f20c960865e85bMindy Pereira if (mPendingChipsCount > 0) { 17363c42baf5b81810bce77e776963f20c960865e85bMindy Pereira return; 17373c42baf5b81810bce77e776963f20c960865e85bMindy Pereira } 17385df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira // Find the last chip. 1739194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] recips = getSortedRecipients(); 17405df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira if (recips != null && recips.length > 0) { 1741194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip last = recips[recips.length - 1]; 1742194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip beforeLast = null; 17435df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira if (recips.length > 1) { 17445df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira beforeLast = recips[recips.length - 2]; 17455df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 17465df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira int startLooking = 0; 17475df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira int end = getSpannable().getSpanStart(last); 17485df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira if (beforeLast != null) { 17495df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1750399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira Editable text = getText(); 175101162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1752399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira // There is nothing after this chip. 1753399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira return; 1754399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira } 1755399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira if (text.charAt(startLooking) == ' ') { 17565df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira startLooking++; 17575df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 17585df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 1759e13775cc6e9342e42db0b853cc42dbfda6d1365fMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking < end) { 17605df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira getText().delete(startLooking, end); 17615df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 17625df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 17635df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira } 17645df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira 176579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private boolean shouldCreateChip(int start, int end) { 17661163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz return !mNoChipMode && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 176732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira } 176832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira 176932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira private boolean alreadyHasChip(int start, int end) { 17701163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (mNoChipMode) { 1771f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return true; 1772f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 1773194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = 1774194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy getSpannable().getSpans(start, end, DrawableRecipientChip.class); 1775ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao return chips != null && chips.length > 0; 177679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 177779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira 177879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private void handleEdit(int start, int end) { 1779a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira if (start == -1 || end == -1) { 1780a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira // This chip no longer exists in the field. 1781a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira dismissDropDown(); 1782a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira return; 1783a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira } 178479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira // This is in the middle of a chip, so select out the whole chip 178579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira // and commit it. 178679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira Editable editable = getText(); 178779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira setSelection(end); 178879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira String text = getText().toString().substring(start, end); 1789fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira if (!TextUtils.isEmpty(text)) { 1790f30a42800318f6790d55421f8f6980eb38db4d3cmindyp RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text)); 1791fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1792ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao CharSequence chipText = createChip(entry); 17935cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira int selEnd = getSelectionEnd(); 17945cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira if (chipText != null && start > -1 && selEnd > -1) { 17955cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira editable.replace(start, selEnd, chipText); 17962d7709d276c03e536d37961076107af9f98522f5Mindy Pereira } 1797fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira } 1798ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira dismissDropDown(); 179964af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira } 180064af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira 1801a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 1802a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * If there is a selected chip, delegate the key events 1803a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * to the selected chip. 1804a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 1805cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 1806ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { 180795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 180895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 180995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mAlternatesPopup.dismiss(); 181095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 181195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira removeChip(mSelectedChip); 1812cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1813cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 18142fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy switch (keyCode) { 18152fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy case KeyEvent.KEYCODE_ENTER: 18162fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy case KeyEvent.KEYCODE_DPAD_CENTER: 18172fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy if (event.hasNoModifiers()) { 18182fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy if (commitDefault()) { 18192fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy return true; 18202fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy } 18212fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy if (mSelectedChip != null) { 18222fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy clearSelectedChip(); 18232fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy return true; 18242fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy } else if (focusNext()) { 18252fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy return true; 18262fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy } 18272fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy } 18282fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy break; 18292fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy } 18302fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy 18318cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz final DrawableRecipientChip lastRecipientChip = getLastChip(); 18328cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz boolean isHandled = super.onKeyDown(keyCode, event); 18338cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz 18348cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz /* 18358cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz * Hacky way to report a deleted chip: 18368cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz * In some devices/configurations, {@link KeyEvent#KEYCODE_DEL} character is causing 18378cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz * onKeyDown() to be called, which in turns handles the chip deletion instead of 18388cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz * {@link RecipientTextWatcher#onTextChanged}. We want to call 18398cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz * {@link RecipientChipDeletedListener#onRecipientChipDeleted} callback for these cases. 18408cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz */ 18418cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz if (keyCode == KeyEvent.KEYCODE_DEL && isHandled && lastRecipientChip != null) { 18428cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz final RecipientEntry entry = lastRecipientChip.getEntry(); 18438cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz if (!mNoChipMode && mRecipientChipDeletedListener != null && entry != null) { 18448cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz mRecipientChipDeletedListener.onRecipientChipDeleted(entry); 18458cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz } 18468cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz } 18478cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz 18488cd6353bdce56d423a9381f2efbc20d26f600710Ekin Oguz return isHandled; 18499159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira } 18509159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira 185101162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira // Visible for testing. 185201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira /* package */ Spannable getSpannable() { 1853e33555f13a9b05d835cb860e2c30ef40af3c8502Erik return getText(); 1854cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1855cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1856194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private int getChipStart(DrawableRecipientChip chip) { 185795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira return getSpannable().getSpanStart(chip); 185895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 185995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira 1860194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private int getChipEnd(DrawableRecipientChip chip) { 186195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira return getSpannable().getSpanEnd(chip); 186295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 186395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira 1864cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira /** 1865cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira * Instead of filtering on the entire contents of the edit box, 1866cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira * this subclass method filters on the range from 1867cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1868cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1869cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira * and makes sure that the range is not already a Chip. 1870cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira */ 1871cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 1872f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic public void performFiltering(@NonNull CharSequence text, int keyCode) { 1873093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp boolean isCompletedToken = isCompletedToken(text); 1874093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp if (enoughToFilter() && !isCompletedToken) { 1875cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int end = getSelectionEnd(); 1876cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1877cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // If this is a RecipientChip, don't filter 1878cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // on its contents. 1879cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira Spannable span = getSpannable(); 1880194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class); 1881cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira if (chips != null && chips.length > 0) { 188241e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin dismissDropDown(); 1883cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return; 1884cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1885093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp } else if (isCompletedToken) { 188641e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin dismissDropDown(); 1887093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp return; 1888cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1889cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira super.performFiltering(text, keyCode); 1890cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1891cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 189220c9d620e79ae28994856541761a951074551518Mindy Pereira // Visible for testing. 189320c9d620e79ae28994856541761a951074551518Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 189420c9d620e79ae28994856541761a951074551518Mindy Pereira if (TextUtils.isEmpty(text)) { 189520c9d620e79ae28994856541761a951074551518Mindy Pereira return false; 189620c9d620e79ae28994856541761a951074551518Mindy Pereira } 189720c9d620e79ae28994856541761a951074551518Mindy Pereira // Check to see if this is a completed token before filtering. 189820c9d620e79ae28994856541761a951074551518Mindy Pereira int end = text.length(); 189920c9d620e79ae28994856541761a951074551518Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 190020c9d620e79ae28994856541761a951074551518Mindy Pereira String token = text.toString().substring(start, end).trim(); 190120c9d620e79ae28994856541761a951074551518Mindy Pereira if (!TextUtils.isEmpty(token)) { 190220c9d620e79ae28994856541761a951074551518Mindy Pereira char atEnd = token.charAt(token.length() - 1); 190320c9d620e79ae28994856541761a951074551518Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 190420c9d620e79ae28994856541761a951074551518Mindy Pereira } 190520c9d620e79ae28994856541761a951074551518Mindy Pereira return false; 190620c9d620e79ae28994856541761a951074551518Mindy Pereira } 190720c9d620e79ae28994856541761a951074551518Mindy Pereira 190849744b029d48dd9082bc02cb1c00dc34d1619c85Jin Cao /** 190949744b029d48dd9082bc02cb1c00dc34d1619c85Jin Cao * Clears the selected chip if there is one (and dismissing any popups related to the selected 191049744b029d48dd9082bc02cb1c00dc34d1619c85Jin Cao * chip in the process). 191149744b029d48dd9082bc02cb1c00dc34d1619c85Jin Cao */ 191249744b029d48dd9082bc02cb1c00dc34d1619c85Jin Cao public void clearSelectedChip() { 1913cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira if (mSelectedChip != null) { 191495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira unselectChip(mSelectedChip); 1915cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira mSelectedChip = null; 1916cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1917b86dcd5230ebcc57e5fc7a669c2304aca142dbf5Mindy Pereira setCursorVisible(true); 1918ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao setSelection(getText().length()); 1919cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1920cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1921a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 1922a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * Monitor touch events in the RecipientEditTextView. 1923a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * If the view does not have focus, any tap on the view 1924a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * will just focus the view. If the view has focus, determine 1925a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 1926a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * is not selected, select it and clear any other selected chips. 1927a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * If it isn't, then select that chip. 1928a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 1929cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 1930ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public boolean onTouchEvent(@NonNull MotionEvent event) { 1931e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam boolean handled; 193205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira int action = event.getAction(); 1933e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam final float x = event.getX(); 1934e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam final float y = event.getY(); 1935e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam final int offset = putOffsetInRange(x, y); 1936e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam final DrawableRecipientChip currentChip = findChip(offset); 1937c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein if (action == MotionEvent.ACTION_UP) { 1938e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam boolean touchedWarningIcon = touchedWarningIcon(x, y, currentChip); 1939e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam if (touchedWarningIcon) { 1940e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam String warningText = String.format(mWarningTextTemplate, 1941e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam currentChip.getEntry().getDestination()); 1942e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam showWarningDialog(warningText); 1943e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam return true; 1944e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1945e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (!isFocused()) { 1946e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // Ignore further chip taps until this view is focused. 1947e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return touchedWarningIcon || super.onTouchEvent(event); 1948e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1949e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam handled = super.onTouchEvent(event); 1950e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (mSelectedChip == null) { 1951e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mGestureDetector.onTouchEvent(event); 1952e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1953e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam boolean chipWasSelected = false; 1954cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira if (currentChip != null) { 1955ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao if (mSelectedChip != null && mSelectedChip != currentChip) { 1956ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao clearSelectedChip(); 1957ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao selectChip(currentChip); 1958ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao } else if (mSelectedChip == null) { 1959ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao commitDefault(); 1960ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao selectChip(currentChip); 1961ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao } else { 1962ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao onClick(mSelectedChip); 1963cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1964cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira chipWasSelected = true; 1965c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira handled = true; 19665e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) { 1967c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira chipWasSelected = true; 1968cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1969e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (!chipWasSelected) { 1970e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam clearSelectedChip(); 1971e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1972e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } else { 1973e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam boolean touchedWarningIcon = touchedWarningIcon(x, y, currentChip); 1974e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam if (touchedWarningIcon) { 1975e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam return true; 1976e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam } 1977e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam handled = super.onTouchEvent(event); 1978e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (!isFocused()) { 1979e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam return handled; 1980e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1981e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam if (mSelectedChip == null) { 1982e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam mGestureDetector.onTouchEvent(event); 1983e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam } 1984cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1985cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return handled; 1986cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 1987cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 1988e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam private boolean touchedWarningIcon(float x, float y, DrawableRecipientChip currentChip) { 1989e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam boolean touchedWarningIcon = false; 1990e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam if (currentChip != null) { 1991e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam Rect outOfDomainWarningBounds = currentChip.getWarningIconBounds(); 1992e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam if (outOfDomainWarningBounds != null) { 19931c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam int chipLeftOffset = shouldPositionAvatarOnRight() 19941c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam ? getChipEnd(currentChip) : getChipStart(currentChip); 19951c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam float chipLeftPosition = this.getLayout().getPrimaryHorizontal(chipLeftOffset); 19961c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam float chipTopPosition = this.getLayout().getLineTop( 19971c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam this.getLayout().getLineForOffset(chipLeftOffset)) + getTotalPaddingTop(); 1998e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam final RectF touchOutOfDomainWarning = new RectF( 19991c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam chipLeftPosition + outOfDomainWarningBounds.left, 20001c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam chipTopPosition + outOfDomainWarningBounds.top, 20011c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam chipLeftPosition + outOfDomainWarningBounds.right, 20021c4ebfe46e0c7088d377763b92dd4b8ea89c6fb8Joseph Moghadam chipTopPosition + outOfDomainWarningBounds.bottom); 2003e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam touchedWarningIcon = touchOutOfDomainWarning.contains(x, y); 2004e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam } 2005e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam } 2006e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam return touchedWarningIcon; 2007e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam } 2008e2f29e0ad6e5426955852217686a3d55ec0a7ad7Joseph Moghadam 2009b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam private void showWarningDialog(String warningText) { 2010b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam mCurrentWarningText = warningText; 2011b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam new AlertDialog.Builder(RecipientEditTextView.this.getContext()) 2012b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam .setTitle(mWarningTitle) 2013b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam .setOnDismissListener(new DialogInterface.OnDismissListener() { 2014b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam @Override 2015b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam public void onDismiss(DialogInterface dialog) { 2016b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam mCurrentWarningText = ""; 2017b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam } 2018b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam }) 2019b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam .setMessage(mCurrentWarningText) 2020b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam .show(); 2021b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam } 2022b37b09e7269db1d135abcd9ff0660f9ee00a83c4Joseph Moghadam 2023272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy private void showAlternates(final DrawableRecipientChip currentChip, 20249b166209d723ed996243de5d89b489f3992daf0eJin Cao final ListPopupWindow alternatesPopup) { 2025272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy new AsyncTask<Void, Void, ListAdapter>() { 2026272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy @Override 2027272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy protected ListAdapter doInBackground(final Void... params) { 2028272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy return createAlternatesAdapter(currentChip); 2029272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy } 2030272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy 20316b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang @Override 2032272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy protected void onPostExecute(final ListAdapter result) { 2033fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler if (!mAttachedToWindow) { 2034fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler return; 2035fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler } 2036272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy int line = getLayout().getLineForOffset(getChipStart(currentChip)); 20376fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao int bottomOffset = calculateOffsetFromBottomToTop(line); 20386fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao 2039272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // Align the alternates popup with the left side of the View, 2040272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // regardless of the position of the chip tapped. 20414de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao alternatesPopup.setAnchorView((mAlternatePopupAnchor != null) ? 20424de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao mAlternatePopupAnchor : RecipientEditTextView.this); 20436fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao alternatesPopup.setVerticalOffset(bottomOffset); 2044272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy alternatesPopup.setAdapter(result); 2045272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy alternatesPopup.setOnItemClickListener(mAlternatesListener); 2046272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // Clear the checked item. 2047272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy mCheckedItem = -1; 2048272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy alternatesPopup.show(); 2049272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy ListView listView = alternatesPopup.getListView(); 2050272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 2051272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // Checked item would be -1 if the adapter has not 2052272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // loaded the view that should be checked yet. The 2053272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // variable will be set correctly when onCheckedItemChanged 2054272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy // is called in a separate thread. 2055272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy if (mCheckedItem != -1) { 2056272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy listView.setItemChecked(mCheckedItem, true); 2057272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy mCheckedItem = -1; 2058272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy } 2059272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy } 2060272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy }.execute((Void[]) null); 206195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 206295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira 20636e96c08f77c783d7eb7a926151f16f95255205adMichael Berberet protected ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) { 20647a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), 20657a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(), 20664db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao getAdapter().getQueryType(), this, mDropdownChipLayouter, 20674b01e3b35a9634336e5de163ba7c4110785bf221Milos Stankovic constructStateListDeleteDrawable(), getAdapter().getPermissionsCheckListener()); 206895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 206995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira 2070194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) { 2071b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(), 20724db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao mDropdownChipLayouter, constructStateListDeleteDrawable()); 20734db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao } 20744db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao 20754db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao private StateListDrawable constructStateListDeleteDrawable() { 20764db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao // Construct the StateListDrawable from deleteDrawable 20774db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao StateListDrawable deleteDrawable = new StateListDrawable(); 2078a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao if (!mDisableDelete) { 2079a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao deleteDrawable.addState(new int[]{android.R.attr.state_activated}, mChipDelete); 2080a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao } 20814db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao deleteDrawable.addState(new int[0], null); 20824db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao return deleteDrawable; 208301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 208401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira 2085aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler @Override 208695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira public void onCheckedItemChanged(int position) { 208795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 208895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 208995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira listView.setItemChecked(position, true); 209095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 209198b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira mCheckedItem = position; 209295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 209395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira 20941650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy private int putOffsetInRange(final float x, final float y) { 20951650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy final int offset; 20961650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 20971650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 20981650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy offset = getOffsetForPosition(x, y); 20991650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } else { 21001650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy offset = supportGetOffsetForPosition(x, y); 21011650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 21021650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 21031650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy return putOffsetInRange(offset); 21041650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 21051650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 2106cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 2107cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 2108cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // what comes before the finger. 2109cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira private int putOffsetInRange(int o) { 2110cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int offset = o; 2111cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira Editable text = getText(); 2112cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int length = text.length(); 2113cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Remove whitespace from end to find "real end" 2114cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int realLength = length; 2115cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira for (int i = length - 1; i >= 0; i--) { 2116cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira if (text.charAt(i) == ' ') { 2117cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira realLength--; 2118cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } else { 2119cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira break; 2120cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2121cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2122cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 2123fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira // If the offset is beyond or at the end of the text, 2124fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira // leave it alone. 2125fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira if (offset >= realLength) { 2126cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return offset; 2127cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2128fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira Editable editable = getText(); 2129b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 2130cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Keep walking backward! 2131cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira offset--; 2132cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2133cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return offset; 2134cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2135cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 2136f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy private static int findText(Editable text, int offset) { 2137fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira if (text.charAt(offset) != ' ') { 2138fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira return offset; 2139fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira } 2140fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira return -1; 2141fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira } 2142fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira 2143194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private DrawableRecipientChip findChip(int offset) { 2144ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao final Spannable span = getSpannable(); 2145ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao final DrawableRecipientChip[] chips = 2146ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao span.getSpans(0, span.length(), DrawableRecipientChip.class); 2147cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Find the chip that contains this offset. 2148ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao for (DrawableRecipientChip chip : chips) { 214995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int start = getChipStart(chip); 215095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int end = getChipEnd(chip); 215195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (offset >= start && offset <= end) { 2152cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return chip; 2153cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2154cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2155cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return null; 2156cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2157cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 215801162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira // Visible for testing. 2159aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Use this method to generate text to add to the list of addresses. 216032aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira /* package */String createAddressText(RecipientEntry entry) { 21613ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira String display = entry.getDisplayName(); 21623ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira String address = entry.getDestination(); 21633ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 21643ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira display = null; 21653ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira } 2166abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor String trimmedDisplayText; 216723f4fd2e3581f29e164d4b62a9095ab1e492c009Michael Berberet if (isPhoneQuery() && PhoneUtil.isPhoneNumber(address)) { 2168abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor trimmedDisplayText = address.trim(); 2169abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor } else { 2170abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor if (address != null) { 2171abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor // Tokenize out the address in case the address already 2172abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor // contained the username as well. 2173abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 2174abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor if (tokenized != null && tokenized.length > 0) { 2175abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor address = tokenized[0].getAddress(); 2176abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor } 2177ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira } 2178abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor Rfc822Token token = new Rfc822Token(display, address, null); 2179abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor trimmedDisplayText = token.toString().trim(); 2180d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira } 21813ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira int index = trimmedDisplayText.indexOf(","); 218232aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira return mTokenizer != null && !TextUtils.isEmpty(trimmedDisplayText) 218332aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira && index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 2184aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira .terminateToken(trimmedDisplayText) : trimmedDisplayText; 2185aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } 2186aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira 2187aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Visible for testing. 2188aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Use this method to generate text to display in a chip. 2189aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira /*package*/ String createChipDisplayText(RecipientEntry entry) { 2190aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira String display = entry.getDisplayName(); 2191aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira String address = entry.getDestination(); 2192aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 2193aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira display = null; 2194aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } 2195aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira if (!TextUtils.isEmpty(display)) { 2196aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira return display; 2197aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } else if (!TextUtils.isEmpty(address)){ 2198aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira return address; 2199aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } else { 2200aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira return new Rfc822Token(display, address, null).toString(); 2201aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira } 22023ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira } 22033ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira 2204ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao private CharSequence createChip(RecipientEntry entry) { 22054db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao final String displayText = createAddressText(entry); 22062d7709d276c03e536d37961076107af9f98522f5Mindy Pereira if (TextUtils.isEmpty(displayText)) { 22072d7709d276c03e536d37961076107af9f98522f5Mindy Pereira return null; 22082d7709d276c03e536d37961076107af9f98522f5Mindy Pereira } 2209cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira // Always leave a blank space at the end of a chip. 22104db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao final int textLength = displayText.length() - 1; 22114db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao final SpannableString chipText = new SpannableString(displayText); 22121163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode) { 2213f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira try { 2214ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao DrawableRecipientChip chip = constructChipSpan(entry); 2215f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira chipText.setSpan(chip, 0, textLength, 2216f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2217f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira chip.setOriginalText(chipText.toString()); 2218f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } catch (NullPointerException e) { 2219f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Log.e(TAG, e.getMessage(), e); 2220f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return null; 2221f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2222cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 22233e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin onChipCreated(entry); 2224cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira return chipText; 2225cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2226cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 2227a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 22283e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin * A callback for subclasses to use to know when a chip was created with the 22293e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin * given RecipientEntry. 22303e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin */ 2231ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz protected void onChipCreated(RecipientEntry entry) { 22321163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode && mRecipientChipAddedListener != null) { 2233ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz mRecipientChipAddedListener.onRecipientChipAdded(entry); 2234ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz } 2235ae2cd1536163c0166679e6b4d64ba2df28588ff3Ekin Oguz } 22363e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin 22373e05cfa55dbb8840adaf3dfcf31a5a30ce129c9eKevin Lin /** 2238a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 2239a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * contact information of the selected item. 2240a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 2241cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira @Override 2242cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 2243114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor if (position < 0) { 2244114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor return; 2245114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor } 22460f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein 2247f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic final RecipientEntry entry = getAdapter().getItem(position); 2248f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic if (entry.getEntryType() == RecipientEntry.ENTRY_TYPE_PERMISSION_REQUEST) { 2249f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic if (mPermissionsRequestItemClickedListener != null) { 2250f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic mPermissionsRequestItemClickedListener 2251f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic .onPermissionsRequestItemClicked(this, entry.getPermissions()); 2252f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 2253f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic return; 2254f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 2255f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 22560f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein final int charactersTyped = submitItemAtPosition(position); 22570f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein if (charactersTyped > -1 && mRecipientEntryItemClickedListener != null) { 22580f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein mRecipientEntryItemClickedListener 22590f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein .onRecipientEntryItemClicked(charactersTyped, position); 22600f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein } 2261cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2262cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 22630f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein private int submitItemAtPosition(int position) { 2264858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position)); 2265f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik if (entry == null) { 22660f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein return -1; 2267f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik } 2268cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira clearComposingText(); 2269cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 2270cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int end = getSelectionEnd(); 2271cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 2272cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 2273cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira Editable editable = getText(); 2274cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 2275ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao CharSequence chip = createChip(entry); 2276005b2e29742d7b9f3ecc505a50b11d91bd44c818Mindy Pereira if (chip != null && start >= 0 && end >= 0) { 2277e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira editable.replace(start, end, chip); 2278e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira } 22795df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira sanitizeBetween(); 22800f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein 22810f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein return end - start; 2282cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 2283cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 22843bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 22853bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira if (item == null) { 22863bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira return null; 22873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 22883bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira final RecipientEntry entry; 22893bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira // If the display name and the address are the same, or if this is a 22903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 22913bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira // recipient that is editable. 22923bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira String destination = item.getDestination(); 22935e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp if (!isPhoneQuery() && item.getContactId() == RecipientEntry.GENERATED_CONTACT) { 22945e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp entry = RecipientEntry.constructGeneratedEntry(item.getDisplayName(), 2295f30a42800318f6790d55421f8f6980eb38db4d3cmindyp destination, item.isValid()); 22965e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } else if (RecipientEntry.isCreatedRecipient(item.getContactId()) 2297b4e244af14950aee7d612612d5406981315d3454Mindy Pereira && (TextUtils.isEmpty(item.getDisplayName()) 2298b4e244af14950aee7d612612d5406981315d3454Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 2299b4e244af14950aee7d612612d5406981315d3454Mindy Pereira || (mValidator != null && !mValidator.isValid(destination)))) { 2300f30a42800318f6790d55421f8f6980eb38db4d3cmindyp entry = RecipientEntry.constructFakeEntry(destination, item.isValid()); 23013bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } else { 23023bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira entry = item; 23033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 23043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira return entry; 23053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira } 23063bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira 230701162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira // Visible for testing. 2308194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy /* package */DrawableRecipientChip[] getSortedRecipients() { 2309194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] recips = getSpannable() 2310194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy .getSpans(0, getText().length(), DrawableRecipientChip.class); 2311194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy ArrayList<DrawableRecipientChip> recipientsList = new ArrayList<DrawableRecipientChip>( 2312194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy Arrays.asList(recips)); 2313e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira final Spannable spannable = getSpannable(); 2314194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy Collections.sort(recipientsList, new Comparator<DrawableRecipientChip>() { 2315e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira 2316e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira @Override 2317194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy public int compare(DrawableRecipientChip first, DrawableRecipientChip second) { 2318e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira int firstStart = spannable.getSpanStart(first); 2319e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira int secondStart = spannable.getSpanStart(second); 2320e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira if (firstStart < secondStart) { 2321e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira return -1; 2322e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } else if (firstStart > secondStart) { 2323e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira return 1; 2324e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } else { 2325e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira return 0; 2326e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 2327e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 2328e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira }); 2329194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy return recipientsList.toArray(new DrawableRecipientChip[recipientsList.size()]); 2330e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 2331e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira 233212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira @Override 233312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 233412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira return false; 233512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 233612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 233712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira @Override 233812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 233912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 234012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 234112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira @Override 234212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 234312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira return false; 234412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 234512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 2346a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 2347a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * No chips are selectable. 2348a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 234912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira @Override 235012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 235112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira return false; 235212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 235312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 2354d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira // Visible for testing. 23556c2a016fb00482339dc66b93822042f9f9e2424fJin Cao /* package */ReplacementDrawableSpan getMoreChip() { 2356dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 2357dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira MoreImageSpan.class); 2358dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 2359dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira } 2360dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira 2361f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira private MoreImageSpan createMoreSpan(int count) { 2362f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira String moreText = String.format(mMoreItem.getText().toString(), count); 23636c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint.set(getPaint()); 23646c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint.setTextSize(mMoreItem.getTextSize()); 23656c2a016fb00482339dc66b93822042f9f9e2424fJin Cao mWorkPaint.setColor(mMoreItem.getCurrentTextColor()); 23666c2a016fb00482339dc66b93822042f9f9e2424fJin Cao final int width = (int) mWorkPaint.measureText(moreText) + mMoreItem.getPaddingLeft() 2367f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira + mMoreItem.getPaddingRight(); 23686c2a016fb00482339dc66b93822042f9f9e2424fJin Cao final int height = (int) mChipHeight; 2369f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 2370f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Canvas canvas = new Canvas(drawable); 2371f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int adjustedHeight = height; 2372f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Layout layout = getLayout(); 2373f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (layout != null) { 2374f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira adjustedHeight -= layout.getLineDescent(0); 2375f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 23766c2a016fb00482339dc66b93822042f9f9e2424fJin Cao canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, mWorkPaint); 2377f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2378f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 2379f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira result.setBounds(0, 0, width, height); 2380f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return new MoreImageSpan(result); 2381f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2382f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2383f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Visible for testing. 2384f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira /*package*/ void createMoreChipPlainText() { 2385f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Take the first <= CHIP_LIMIT addresses and get to the end of the second one. 2386f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Editable text = getText(); 2387f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int start = 0; 2388f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int end = start; 2389f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira for (int i = 0; i < CHIP_LIMIT; i++) { 2390f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira end = movePastTerminators(mTokenizer.findTokenEnd(text, start)); 2391f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira start = end; // move to the next token and get its end. 2392f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2393f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Now, count total addresses. 2394f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int tokenCount = countTokens(text); 2395f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT); 2396f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(end, text.length())); 2397f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2398f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira text.replace(end, text.length(), chipText); 2399f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira mMoreChip = moreSpan; 2400f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2401f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2402f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira // Visible for testing. 2403f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira /* package */int countTokens(Editable text) { 2404f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int tokenCount = 0; 2405f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira int start = 0; 2406f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira while (start < text.length()) { 2407f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira start = movePastTerminators(mTokenizer.findTokenEnd(text, start)); 2408f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira tokenCount++; 2409f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (start >= text.length()) { 2410f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira break; 2411f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2412f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2413f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return tokenCount; 2414f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2415f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2416a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 2417342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 2418342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira * do not fit in the pre-defined available space when the 2419342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira * RecipientEditTextView loses focus. 2420a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 2421d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira // Visible for testing. 2422d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira /* package */ void createMoreChip() { 24231163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (mNoChipMode) { 2424f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira createMoreChipPlainText(); 2425f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return; 2426f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 2427f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2428076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira if (!mShouldShrink) { 2429076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira return; 2430076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira } 24316c2a016fb00482339dc66b93822042f9f9e2424fJin Cao ReplacementDrawableSpan[] tempMore = getSpannable().getSpans(0, getText().length(), 24326c2a016fb00482339dc66b93822042f9f9e2424fJin Cao MoreImageSpan.class); 2433e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira if (tempMore.length > 0) { 2434e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira getSpannable().removeSpan(tempMore[0]); 2435e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 2436194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] recipients = getSortedRecipients(); 2437f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira 2438d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 24393bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira mMoreChip = null; 24403bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira return; 244112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 2442e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira Spannable spannable = getSpannable(); 2443d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira int numRecipients = recipients.length; 244412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 2445f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira MoreImageSpan moreSpan = createMoreSpan(overage); 24461a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz mHiddenSpans = new ArrayList<DrawableRecipientChip>(); 244712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira int totalReplaceStart = 0; 244812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira int totalReplaceEnd = 0; 2449e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira Editable text = getText(); 245021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 24511a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz mHiddenSpans.add(recipients[i]); 2452364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira if (i == numRecipients - overage) { 245321cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 2454364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira } 245521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira if (i == recipients.length - 1) { 245621cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 2457364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira } 2458e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 2459e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 2460e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 2461e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 24621852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 246321cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira spannable.removeSpan(recipients[i]); 246412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 2465f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira if (totalReplaceEnd < text.length()) { 2466f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira totalReplaceEnd = text.length(); 2467f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 24681852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 24691852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 24701852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 247112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 24721852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira text.replace(start, end, chipText); 24733bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira mMoreChip = moreSpan; 24745e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp // If adding the +more chip goes over the limit, resize accordingly. 24755e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp if (!isPhoneQuery() && getLineCount() > mMaxLines) { 24765e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp setMaxLines(getLineCount()); 24775e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } 247812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 247912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 2480a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira /** 2481a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 2482a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira * replaced when the RecipientEditTextView gains focus. 2483a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira */ 2484d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira // Visible for testing. 2485d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira /*package*/ void removeMoreChip() { 248612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira if (mMoreChip != null) { 248712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira Spannable span = getSpannable(); 248812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira span.removeSpan(mMoreChip); 248912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira mMoreChip = null; 24901a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz // Re-add the spans that were hidden. 24911a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz if (mHiddenSpans != null && mHiddenSpans.size() > 0) { 24921a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz // Recreate each hidden span. 2493194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] recipients = getSortedRecipients(); 249454effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira // Start the search for tokens after the last currently visible 249554effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira // chip. 2496ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira if (recipients == null || recipients.length == 0) { 2497ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira return; 2498ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira } 249954effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 250012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira Editable editable = getText(); 25011a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz for (DrawableRecipientChip chip : mHiddenSpans) { 2502e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira int chipStart; 25033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira int chipEnd; 25043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira String token; 2505e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira // Need to find the location of the chip, again. 2506e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira token = (String) chip.getOriginalText(); 25071a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz // As we find the matching recipient for the hidden spans, 250854effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira // reduce the size of the string we need to search. 250954effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira // That way, if there are duplicates, we always find the correct 251054effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira // recipient. 251154effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira chipStart = editable.toString().indexOf(token, end); 251254effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 25130d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira // Only set the span if we found a matching token. 25140d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira if (chipStart != -1) { 25150d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 25160d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 25170d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira } 251812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 25191a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz mHiddenSpans.clear(); 252012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 252112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 252212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira } 252312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira 2524cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira /** 252595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 252695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * selecting the chip will take the contents of the chip and place it at 252795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 252895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 2529ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao * will show a popup window with the address in use highlighted and any other 253095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * alternate addresses for the contact. 253195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * @param currentChip Chip to select. 2532cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira */ 2533ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao private void selectChip(DrawableRecipientChip currentChip) { 25345e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp if (shouldShowEditableText(currentChip)) { 253501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira CharSequence text = currentChip.getValue(); 253601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira Editable editable = getText(); 2537f30a42800318f6790d55421f8f6980eb38db4d3cmindyp Spannable spannable = getSpannable(); 2538f30a42800318f6790d55421f8f6980eb38db4d3cmindyp int spanStart = spannable.getSpanStart(currentChip); 2539f30a42800318f6790d55421f8f6980eb38db4d3cmindyp int spanEnd = spannable.getSpanEnd(currentChip); 2540f30a42800318f6790d55421f8f6980eb38db4d3cmindyp spannable.removeSpan(currentChip); 254115a5ebc25941143d5fb65498ebc835d79351d3adJin Cao // Don't need leading space if it's the only chip 254215a5ebc25941143d5fb65498ebc835d79351d3adJin Cao if (spanEnd - spanStart == editable.length() - 1) { 254315a5ebc25941143d5fb65498ebc835d79351d3adJin Cao spanEnd++; 254415a5ebc25941143d5fb65498ebc835d79351d3adJin Cao } 2545f30a42800318f6790d55421f8f6980eb38db4d3cmindyp editable.delete(spanStart, spanEnd); 254601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira setCursorVisible(true); 254701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira setSelection(editable.length()); 2548f30a42800318f6790d55421f8f6980eb38db4d3cmindyp editable.append(text); 2549ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao mSelectedChip = constructChipSpan( 2550ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao RecipientEntry.constructFakeEntry((String) text, isValid(text.toString()))); 25515ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz 25525ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz /* 25535ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz * Because chip is destroyed and converted into an editable text, we call 25545ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz * {@link RecipientChipDeletedListener#onRecipientChipDeleted}. For the cases where 25555ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz * editable text is not shown (i.e. chip is in user's contact list), chip is focused 25565ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz * and below callback is not called. 25575ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz */ 25581163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode && mRecipientChipDeletedListener != null) { 25595ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz mRecipientChipDeletedListener.onRecipientChipDeleted(currentChip.getEntry()); 25605ae8a7a2389589bf9b139a019460405ab20ea5acEkin Oguz } 25618af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein } else { 25628af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein final boolean showAddress = 25638af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT || 25648af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein getAdapter().forceShowAddress(); 25651163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (showAddress && mNoChipMode) { 2566ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao return; 256781fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira } 2568a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde 2569a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde if (isTouchExplorationEnabled()) { 2570a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde // The chips cannot be touch-explored. However, doing a double-tap results in 2571a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde // the popup being shown for the last chip, which is of no value. 2572a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde return; 2573a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde } 2574a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde 2575ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao mSelectedChip = currentChip; 2576ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao setSelection(getText().getSpanEnd(mSelectedChip)); 2577ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao setCursorVisible(false); 2578ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao 25798af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein if (showAddress) { 2580ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao showAddress(currentChip, mAddressPopup); 258101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } else { 2582ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao showAlternates(currentChip, mAlternatesPopup); 258301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 25840ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira } 258595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 25860ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira 2587a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde private boolean isTouchExplorationEnabled() { 2588a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 2589a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde return false; 2590a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde } 2591a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde 2592a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde final AccessibilityManager accessibilityManager = (AccessibilityManager) 2593a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 2594a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde return accessibilityManager.isTouchExplorationEnabled(); 2595a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde } 2596a6a68f4ea972fe37a9f963447b9b6cc6f7ddadc2Shri Borde 2597194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private boolean shouldShowEditableText(DrawableRecipientChip currentChip) { 25985e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp long contactId = currentChip.getContactId(); 25995e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp return contactId == RecipientEntry.INVALID_CONTACT 26005e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT); 26015e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp } 2602cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 26039b166209d723ed996243de5d89b489f3992daf0eJin Cao private void showAddress(final DrawableRecipientChip currentChip, final ListPopupWindow popup) { 2604fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler if (!mAttachedToWindow) { 2605fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler return; 2606fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler } 260701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 26086fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao int bottomOffset = calculateOffsetFromBottomToTop(line); 260901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira // Align the alternates popup with the left side of the View, 261001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira // regardless of the position of the chip tapped. 26114de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao popup.setAnchorView((mAlternatePopupAnchor != null) ? mAlternatePopupAnchor : this); 26126fc1fee597ebe3c30ebe5efebb4d6ea105641e0fJin Cao popup.setVerticalOffset(bottomOffset); 261301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 261401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 261501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira @Override 261601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 261701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira unselectChip(currentChip); 261801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira popup.dismiss(); 261901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 262001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira }); 262101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira popup.show(); 262201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira ListView listView = popup.getListView(); 262301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 262401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira listView.setItemChecked(0, true); 262501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira } 262601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira 262795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira /** 262895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 2629f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira * the chip without a delete icon and with an unfocused background. This is 2630f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira * called when the RecipientChip no longer has focus. 263195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira */ 2632194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private void unselectChip(DrawableRecipientChip chip) { 263395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int start = getChipStart(chip); 263495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int end = getChipEnd(chip); 263595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira Editable editable = getText(); 2636e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira mSelectedChip = null; 263795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (start == -1 || end == -1) { 2638f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing"); 2639c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira setSelection(editable.length()); 2640c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira commitDefault(); 264195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } else { 2642e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira getSpannable().removeSpan(chip); 264395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 264404da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira editable.removeSpan(chip); 264504da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira try { 26461163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode) { 2647ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao editable.setSpan(constructChipSpan(chip.getEntry()), 26485e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 2649f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 265004da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira } catch (NullPointerException e) { 265104da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira Log.e(TAG, e.getMessage(), e); 265204da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira } 2653cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 265495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira setCursorVisible(true); 265595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira setSelection(editable.length()); 265695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 265795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mAlternatesPopup.dismiss(); 2658cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 265995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 2660cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 26614db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao @Override 26624db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao public void onChipDelete() { 26634db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao if (mSelectedChip != null) { 26641163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode && mRecipientChipDeletedListener != null) { 2665339ffd585c0a033822b8a01d39834d6f5870b04aEkin Oguz mRecipientChipDeletedListener.onRecipientChipDeleted(mSelectedChip.getEntry()); 2666339ffd585c0a033822b8a01d39834d6f5870b04aEkin Oguz } 26674db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao removeChip(mSelectedChip); 26684db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao } 2669568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao dismissPopups(); 2670568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao } 2671568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao 2672f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic @Override 2673f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic public void onPermissionRequestDismissed() { 2674f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic if (mPermissionsRequestItemClickedListener != null) { 2675f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic mPermissionsRequestItemClickedListener.onPermissionRequestDismissed(); 2676f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 2677f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic dismissDropDown(); 2678f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic } 2679f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic 2680568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao private void dismissPopups() { 2681568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 2682568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao mAlternatesPopup.dismiss(); 2683568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao } 2684568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao if (mAddressPopup != null && mAddressPopup.isShowing()) { 2685568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao mAddressPopup.dismiss(); 2686568fb229f9e17ab882cedaa9ee17c9f78a97ba9fJin Cao } 2687ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao setSelection(getText().length()); 26884db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao } 26894db8cccf3332ad7c6fb1915f9f0f169953c3953aJin Cao 269095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira /** 269195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 269295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira */ 269397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira // Visible for testing. 2694b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin /* package */void removeChip(DrawableRecipientChip chip) { 269595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira Spannable spannable = getSpannable(); 269695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int spanStart = spannable.getSpanStart(chip); 269795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 269895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira Editable text = getText(); 269962397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira int toDelete = spanEnd; 270095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira boolean wasSelected = chip == mSelectedChip; 270195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira // Clear that there is a selected chip before updating any text. 270295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (wasSelected) { 270395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mSelectedChip = null; 2704cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 270562397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira // Always remove trailing spaces when removing a chip. 270662397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 270762397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira toDelete++; 270862397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira } 270995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira spannable.removeSpan(chip); 2710f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira if (spanStart >= 0 && toDelete > 0) { 2711f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira text.delete(spanStart, toDelete); 2712f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira } 271395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (wasSelected) { 271495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira clearSelectedChip(); 2715cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 271695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 2717cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 271895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira /** 271995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Replace this currently selected chip with a new chip 272095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * that uses the contact data provided. 272195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira */ 2722aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira // Visible for testing. 2723194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy /*package*/ void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) { 272495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira boolean wasSelected = chip == mSelectedChip; 272595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (wasSelected) { 272695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira mSelectedChip = null; 2727cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 272895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int start = getChipStart(chip); 272995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira int end = getChipEnd(chip); 273095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira getSpannable().removeSpan(chip); 273195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira Editable editable = getText(); 273211c15260cb6878a67eb5d958277fce33e641720fEkin Oguz entry.setInReplacedChip(true); 2733ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao CharSequence chipText = createChip(entry); 27342d7709d276c03e536d37961076107af9f98522f5Mindy Pereira if (chipText != null) { 27352d7709d276c03e536d37961076107af9f98522f5Mindy Pereira if (start == -1 || end == -1) { 27362d7709d276c03e536d37961076107af9f98522f5Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 27372d7709d276c03e536d37961076107af9f98522f5Mindy Pereira editable.insert(0, chipText); 27382d7709d276c03e536d37961076107af9f98522f5Mindy Pereira } else { 27392d7709d276c03e536d37961076107af9f98522f5Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 27402d7709d276c03e536d37961076107af9f98522f5Mindy Pereira // There may be a space to replace with this chip's new 2741b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp // associated space. Check for it 27422d7709d276c03e536d37961076107af9f98522f5Mindy Pereira int toReplace = end; 27432d7709d276c03e536d37961076107af9f98522f5Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 27442d7709d276c03e536d37961076107af9f98522f5Mindy Pereira && editable.charAt(toReplace) == ' ') { 27452d7709d276c03e536d37961076107af9f98522f5Mindy Pereira toReplace++; 27462d7709d276c03e536d37961076107af9f98522f5Mindy Pereira } 27472d7709d276c03e536d37961076107af9f98522f5Mindy Pereira editable.replace(start, toReplace, chipText); 274862397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira } 27497ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira } 2750cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 275195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira setCursorVisible(true); 275295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (wasSelected) { 275395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira clearSelectedChip(); 2754cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 275595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 2756cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 275795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira /** 275895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 275995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 276095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira * Otherwise, unselect the chip. 276195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira */ 2762a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao public void onClick(DrawableRecipientChip chip) { 276395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira if (chip.isSelected()) { 2764a21fd887528a3b985f71decb2a19c917c2fcbd55Jin Cao clearSelectedChip(); 2765cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira } 276695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira } 2767cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira 276821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira private boolean chipsPending() { 27691a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz return mPendingChipsCount > 0 || (mHiddenSpans != null && mHiddenSpans.size() > 0); 277021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira } 277121cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira 2772883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira @Override 2773883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 2774883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira mTextWatcher = null; 2775883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira super.removeTextChangedListener(watcher); 2776883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira } 2777883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira 2778b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao private boolean isValidEmailAddress(String input) { 2779b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao return !TextUtils.isEmpty(input) && mValidator != null && 2780b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao mValidator.isValid(input); 2781b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao } 2782b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao 278379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira private class RecipientTextWatcher implements TextWatcher { 278472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira 278579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira @Override 278679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira public void afterTextChanged(Editable s) { 278732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira // If the text has been set to null or empty, make sure we remove 278832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira // all the spans we applied. 278932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira if (TextUtils.isEmpty(s)) { 279032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira // Remove all the chips spans. 279132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira Spannable spannable = getSpannable(); 2792194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(), 2793194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip.class); 2794194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (DrawableRecipientChip chip : chips) { 279532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira spannable.removeSpan(chip); 279632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira } 279732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira if (mMoreChip != null) { 279832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira spannable.removeSpan(mMoreChip); 279932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira } 280041e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin clearSelectedChip(); 280132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira return; 280232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira } 280301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira // Get whether there are any recipients pending addition to the 280401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira // view. If there are, don't do anything in the text watcher. 280521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira if (chipsPending()) { 280679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira return; 280779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 2808c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira // If the user is editing a chip, don't clear it. 2809f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (mSelectedChip != null) { 2810f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (!isGeneratedContact(mSelectedChip)) { 2811f30a42800318f6790d55421f8f6980eb38db4d3cmindyp setCursorVisible(true); 2812f30a42800318f6790d55421f8f6980eb38db4d3cmindyp setSelection(getText().length()); 2813f30a42800318f6790d55421f8f6980eb38db4d3cmindyp clearSelectedChip(); 2814f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } else { 2815f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return; 2816f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } 281779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 281879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira int length = s.length(); 281979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira // Make sure there is content there to parse and that it is 2820ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira // not just the commit character. 282179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira if (length > 1) { 2822f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (lastCharacterIsCommitCharacter(s)) { 2823f30a42800318f6790d55421f8f6980eb38db4d3cmindyp commitByCharacter(); 2824f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return; 2825f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } 2826ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira char last; 282711a2adb53f764ffcadd262678ff782e6b4992decMindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 2828ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira int len = length() - 1; 2829ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira if (end != len) { 2830ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira last = s.charAt(end); 2831ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira } else { 2832ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira last = s.charAt(len); 2833ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira } 2834f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (last == COMMIT_CHAR_SPACE) { 28354afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor if (!isPhoneQuery()) { 28364afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor // Check if this is a valid email address. If it is, 28374afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor // commit it. 28384afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor String text = getText().toString(); 28394afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 28404afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 28414afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor tokenStart)); 2842b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao if (isValidEmailAddress(sub)) { 28434afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor commitByCharacter(); 28444afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor } 284579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 284679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 284779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 284879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 284979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira 285079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira @Override 285179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 2852f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // The user deleted some text OR some text was replaced; check to 2853f30a42800318f6790d55421f8f6980eb38db4d3cmindyp // see if the insertion point is on a space 285472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira // following a chip. 2855f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (before - count == 1) { 285672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira // If the item deleted is a space, and the thing before the 285772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira // space is a chip, delete the entire span. 285872a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira int selStart = getSelectionStart(); 2859194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart, 2860194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip.class); 286172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira if (repl.length > 0) { 286272a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira // There is a chip there! Just remove it. 2863ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao DrawableRecipientChip toDelete = repl[0]; 286472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira Editable editable = getText(); 286572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira // Add the separator token. 2866ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao int deleteStart = editable.getSpanStart(toDelete); 2867ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao int deleteEnd = editable.getSpanEnd(toDelete) + 1; 2868ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao if (deleteEnd > editable.length()) { 2869ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao deleteEnd = editable.length(); 287062fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira } 28711163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (!mNoChipMode && mRecipientChipDeletedListener != null) { 2872c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko mRecipientChipDeletedListener.onRecipientChipDeleted(toDelete.getEntry()); 2873c4da98ae5163692b343b3f4c034a79840b1f3995Borys Boyko } 2874ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao editable.removeSpan(toDelete); 2875ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao editable.delete(deleteStart, deleteEnd); 287672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira } 28774e7d20f7b74ddce5a104138714c9683c8bd2300dMindy Pereira } else if (count > before) { 28787e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp if (mSelectedChip != null 28797e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp && isGeneratedContact(mSelectedChip)) { 28807e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp if (lastCharacterIsCommitCharacter(s)) { 28817e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp commitByCharacter(); 28827e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp return; 28837e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp } 28847e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp } 288572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira } 288679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 288779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira 288879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira @Override 288979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 2890dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira // Do nothing. 289179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 289279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira } 28931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira 2894f1ad4f6dba7c6ea28f529736b8662faa6fb498f8Milos Stankovic public boolean lastCharacterIsCommitCharacter(CharSequence s) { 2895f30a42800318f6790d55421f8f6980eb38db4d3cmindyp char last; 2896f30a42800318f6790d55421f8f6980eb38db4d3cmindyp int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 2897f30a42800318f6790d55421f8f6980eb38db4d3cmindyp int len = length() - 1; 2898f30a42800318f6790d55421f8f6980eb38db4d3cmindyp if (end != len) { 2899f30a42800318f6790d55421f8f6980eb38db4d3cmindyp last = s.charAt(end); 2900f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } else { 2901f30a42800318f6790d55421f8f6980eb38db4d3cmindyp last = s.charAt(len); 2902f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } 2903f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return last == COMMIT_CHAR_COMMA || last == COMMIT_CHAR_SEMICOLON; 2904f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } 2905f30a42800318f6790d55421f8f6980eb38db4d3cmindyp 2906194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy public boolean isGeneratedContact(DrawableRecipientChip chip) { 2907f30a42800318f6790d55421f8f6980eb38db4d3cmindyp long contactId = chip.getContactId(); 2908f30a42800318f6790d55421f8f6980eb38db4d3cmindyp return contactId == RecipientEntry.INVALID_CONTACT 2909f30a42800318f6790d55421f8f6980eb38db4d3cmindyp || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT); 2910f30a42800318f6790d55421f8f6980eb38db4d3cmindyp } 2911f30a42800318f6790d55421f8f6980eb38db4d3cmindyp 2912e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham /** 2913e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}. 2914e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham */ 2915219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps // Visible for testing. 2916219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps void handlePasteClip(ClipData clip) { 2917219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps if (clip == null) { 2918219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps // Do nothing. 2919219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps return; 2920219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps } 2921219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps 2922219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final ClipDescription clipDesc = clip.getDescription(); 2923e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam boolean containsSupportedType = clipDesc.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) 2924e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam || clipDesc.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); 2925219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps if (!containsSupportedType) { 2926219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps return; 2927219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps } 2928219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps 2929e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham removeTextChangedListener(mTextWatcher); 2930e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 2931219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final ClipDescription clipDescription = clip.getDescription(); 2932219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps for (int i = 0; i < clip.getItemCount(); i++) { 2933219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final String mimeType = clipDescription.getMimeType(i); 2934e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam final boolean supportedType = ClipDescription.MIMETYPE_TEXT_PLAIN.equals(mimeType) 2935e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam || ClipDescription.MIMETYPE_TEXT_HTML.equals(mimeType); 2936219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps if (!supportedType) { 2937219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps // Only plain text and html can be pasted. 2938219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps continue; 2939219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps } 2940219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps 2941219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final CharSequence pastedItem = clip.getItemAt(i).getText(); 2942219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps if (!TextUtils.isEmpty(pastedItem)) { 2943219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final Editable editable = getText(); 2944219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final int start = getSelectionStart(); 2945219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps final int end = getSelectionEnd(); 2946219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps if (start < 0 || end < 1) { 2947219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps // No selection. 2948219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps editable.append(pastedItem); 2949219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps } else if (start == end) { 2950219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps // Insert at position. 2951219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps editable.insert(start, pastedItem); 2952219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps } else { 2953219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps editable.append(pastedItem, start, end); 2954e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 2955219cbfbd7806b17ce10ba2cfb3f0cd45d167d46cRégis Décamps handlePasteAndReplace(); 2956e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 2957e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 2958e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 2959e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham mHandler.post(mAddTextWatcher); 2960e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 2961e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 296220c9d620e79ae28994856541761a951074551518Mindy Pereira @Override 296320c9d620e79ae28994856541761a951074551518Mindy Pereira public boolean onTextContextMenuItem(int id) { 296420c9d620e79ae28994856541761a951074551518Mindy Pereira if (id == android.R.id.paste) { 296520c9d620e79ae28994856541761a951074551518Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 296620c9d620e79ae28994856541761a951074551518Mindy Pereira Context.CLIPBOARD_SERVICE); 2967e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham handlePasteClip(clipboard.getPrimaryClip()); 296820c9d620e79ae28994856541761a951074551518Mindy Pereira return true; 296920c9d620e79ae28994856541761a951074551518Mindy Pereira } 297020c9d620e79ae28994856541761a951074551518Mindy Pereira return super.onTextContextMenuItem(id); 297120c9d620e79ae28994856541761a951074551518Mindy Pereira } 297220c9d620e79ae28994856541761a951074551518Mindy Pereira 2973b4e244af14950aee7d612612d5406981315d3454Mindy Pereira private void handlePasteAndReplace() { 2974194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy ArrayList<DrawableRecipientChip> created = handlePaste(); 2975b4e244af14950aee7d612612d5406981315d3454Mindy Pereira if (created != null && created.size() > 0) { 2976b4e244af14950aee7d612612d5406981315d3454Mindy Pereira // Perform reverse lookups on the pasted contacts. 2977b4e244af14950aee7d612612d5406981315d3454Mindy Pereira IndividualReplacementTask replace = new IndividualReplacementTask(); 2978b4e244af14950aee7d612612d5406981315d3454Mindy Pereira replace.execute(created); 2979b4e244af14950aee7d612612d5406981315d3454Mindy Pereira } 2980b4e244af14950aee7d612612d5406981315d3454Mindy Pereira } 2981b4e244af14950aee7d612612d5406981315d3454Mindy Pereira 298220c9d620e79ae28994856541761a951074551518Mindy Pereira // Visible for testing. 2983194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy /* package */ArrayList<DrawableRecipientChip> handlePaste() { 298420c9d620e79ae28994856541761a951074551518Mindy Pereira String text = getText().toString(); 298520c9d620e79ae28994856541761a951074551518Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 298620c9d620e79ae28994856541761a951074551518Mindy Pereira String lastAddress = text.substring(originalTokenStart); 298720c9d620e79ae28994856541761a951074551518Mindy Pereira int tokenStart = originalTokenStart; 29880cae85010120a2a8490baf3488ed0bd2293e1d4djli int prevTokenStart = 0; 2989194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip findChip = null; 2990194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy ArrayList<DrawableRecipientChip> created = new ArrayList<DrawableRecipientChip>(); 299120c9d620e79ae28994856541761a951074551518Mindy Pereira if (tokenStart != 0) { 299220c9d620e79ae28994856541761a951074551518Mindy Pereira // There are things before this! 29930cae85010120a2a8490baf3488ed0bd2293e1d4djli while (tokenStart != 0 && findChip == null && tokenStart != prevTokenStart) { 299420c9d620e79ae28994856541761a951074551518Mindy Pereira prevTokenStart = tokenStart; 299520c9d620e79ae28994856541761a951074551518Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 299620c9d620e79ae28994856541761a951074551518Mindy Pereira findChip = findChip(tokenStart); 2997fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor if (tokenStart == originalTokenStart && findChip == null) { 2998fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor break; 2999fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor } 300020c9d620e79ae28994856541761a951074551518Mindy Pereira } 300120c9d620e79ae28994856541761a951074551518Mindy Pereira if (tokenStart != originalTokenStart) { 300220c9d620e79ae28994856541761a951074551518Mindy Pereira if (findChip != null) { 300320c9d620e79ae28994856541761a951074551518Mindy Pereira tokenStart = prevTokenStart; 300420c9d620e79ae28994856541761a951074551518Mindy Pereira } 300520c9d620e79ae28994856541761a951074551518Mindy Pereira int tokenEnd; 3006194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip createdChip; 300720c9d620e79ae28994856541761a951074551518Mindy Pereira while (tokenStart < originalTokenStart) { 3008b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(getText().toString(), 3009b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp tokenStart)); 301020c9d620e79ae28994856541761a951074551518Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 301120c9d620e79ae28994856541761a951074551518Mindy Pereira createdChip = findChip(tokenStart); 3012c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira if (createdChip == null) { 3013c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira break; 3014c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira } 301520c9d620e79ae28994856541761a951074551518Mindy Pereira // +1 for the space at the end. 301620c9d620e79ae28994856541761a951074551518Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 3017b4e244af14950aee7d612612d5406981315d3454Mindy Pereira created.add(createdChip); 301820c9d620e79ae28994856541761a951074551518Mindy Pereira } 301920c9d620e79ae28994856541761a951074551518Mindy Pereira } 302020c9d620e79ae28994856541761a951074551518Mindy Pereira } 302120c9d620e79ae28994856541761a951074551518Mindy Pereira // Take a look at the last token. If the token has been completed with a 302220c9d620e79ae28994856541761a951074551518Mindy Pereira // commit character, create a chip. 302320c9d620e79ae28994856541761a951074551518Mindy Pereira if (isCompletedToken(lastAddress)) { 302420c9d620e79ae28994856541761a951074551518Mindy Pereira Editable editable = getText(); 3025b4e244af14950aee7d612612d5406981315d3454Mindy Pereira tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart); 3026b4e244af14950aee7d612612d5406981315d3454Mindy Pereira commitChip(tokenStart, editable.length(), editable); 3027b4e244af14950aee7d612612d5406981315d3454Mindy Pereira created.add(findChip(tokenStart)); 302820c9d620e79ae28994856541761a951074551518Mindy Pereira } 3029b4e244af14950aee7d612612d5406981315d3454Mindy Pereira return created; 303020c9d620e79ae28994856541761a951074551518Mindy Pereira } 303120c9d620e79ae28994856541761a951074551518Mindy Pereira 303220c9d620e79ae28994856541761a951074551518Mindy Pereira // Visible for testing. 303320c9d620e79ae28994856541761a951074551518Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 303420c9d620e79ae28994856541761a951074551518Mindy Pereira if (tokenEnd >= length()) { 303520c9d620e79ae28994856541761a951074551518Mindy Pereira return tokenEnd; 303620c9d620e79ae28994856541761a951074551518Mindy Pereira } 303720c9d620e79ae28994856541761a951074551518Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 303820c9d620e79ae28994856541761a951074551518Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 303920c9d620e79ae28994856541761a951074551518Mindy Pereira tokenEnd++; 304020c9d620e79ae28994856541761a951074551518Mindy Pereira } 304120c9d620e79ae28994856541761a951074551518Mindy Pereira // This token had not only an end token character, but also a space 304220c9d620e79ae28994856541761a951074551518Mindy Pereira // separating it from the next token. 304320c9d620e79ae28994856541761a951074551518Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 304420c9d620e79ae28994856541761a951074551518Mindy Pereira tokenEnd++; 304520c9d620e79ae28994856541761a951074551518Mindy Pereira } 304620c9d620e79ae28994856541761a951074551518Mindy Pereira return tokenEnd; 304720c9d620e79ae28994856541761a951074551518Mindy Pereira } 304820c9d620e79ae28994856541761a951074551518Mindy Pereira 30491852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 3050194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private DrawableRecipientChip createFreeChip(RecipientEntry entry) { 30511852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira try { 30521163a27340e6b18ef630fee4dd07c74c579c697cEkin Oguz if (mNoChipMode) { 3053f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira return null; 3054f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira } 3055ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao return constructChipSpan(entry); 30561852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } catch (NullPointerException e) { 30571852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira Log.e(TAG, e.getMessage(), e); 30581852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira return null; 30591852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 30601852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 30611852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira 30621852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira @Override 306378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy protected void onPreExecute() { 306478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // Ensure everything is in chip-form already, so we don't have text that slowly gets 306578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // replaced 3066194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final List<DrawableRecipientChip> originalRecipients = 3067194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy new ArrayList<DrawableRecipientChip>(); 3068194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final DrawableRecipientChip[] existingChips = getSortedRecipients(); 3069ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao Collections.addAll(originalRecipients, existingChips); 30701a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz if (mHiddenSpans != null) { 30711a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz originalRecipients.addAll(mHiddenSpans); 307278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 307378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 3074194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final List<DrawableRecipientChip> replacements = 3075194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy new ArrayList<DrawableRecipientChip>(originalRecipients.size()); 307678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 3077194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (final DrawableRecipientChip chip : originalRecipients) { 307878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId()) 307978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy && getSpannable().getSpanStart(chip) != -1) { 308078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy replacements.add(createFreeChip(chip.getEntry())); 308178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } else { 308278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy replacements.add(null); 308378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 308478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 308578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 308678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy processReplacements(originalRecipients, replacements); 308778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 308878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 308978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy @Override 30901852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira protected Void doInBackground(Void... params) { 30911852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira if (mIndividualReplacements != null) { 30921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira mIndividualReplacements.cancel(true); 30931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 30941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // For each chip in the list, look up the matching contact. 30951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // If there is a match, replace that chip with the matching 30961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // chip. 3097194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final ArrayList<DrawableRecipientChip> recipients = 3098194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy new ArrayList<DrawableRecipientChip>(); 3099194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip[] existingChips = getSortedRecipients(); 3100ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao Collections.addAll(recipients, existingChips); 31011a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz if (mHiddenSpans != null) { 31021a68eb2ed0497378aa39080e3ba54ddf5eff7214Ekin Oguz recipients.addAll(mHiddenSpans); 31031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 310403cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira ArrayList<String> addresses = new ArrayList<String>(); 3105ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao for (DrawableRecipientChip chip : recipients) { 3106f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira if (chip != null) { 310703cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira addresses.add(createAddressText(chip.getEntry())); 3108f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira } 31091852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 31107a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy final BaseRecipientAdapter adapter = getAdapter(); 31118af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() { 311216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp @Override 311394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy public void matchesFound(Map<String, RecipientEntry> entries) { 3114194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final ArrayList<DrawableRecipientChip> replacements = 3115194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy new ArrayList<DrawableRecipientChip>(); 3116194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (final DrawableRecipientChip temp : recipients) { 311716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp RecipientEntry entry = null; 3118f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang if (temp != null && RecipientEntry.isCreatedRecipient( 3119f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang temp.getEntry().getContactId()) 312016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp && getSpannable().getSpanStart(temp) != -1) { 312116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp // Replace this. 312216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp entry = createValidatedEntry( 312316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp entries.get(tokenizeAddress(temp.getEntry() 312416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp .getDestination()))); 312516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 312616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp if (entry != null) { 312716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp replacements.add(createFreeChip(entry)); 312816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } else { 312916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp replacements.add(null); 313016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 313116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 313278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy processReplacements(recipients, replacements); 31331852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 313494fa301de71cc110671802eba8376c275b4055a4Scott Kennedy 313594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy @Override 3136f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy public void matchesNotFound(final Set<String> unfoundAddresses) { 3137194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final List<DrawableRecipientChip> replacements = 3138194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy new ArrayList<DrawableRecipientChip>(unfoundAddresses.size()); 313994fa301de71cc110671802eba8376c275b4055a4Scott Kennedy 3140194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (final DrawableRecipientChip temp : recipients) { 3141ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang if (temp != null && RecipientEntry.isCreatedRecipient( 3142ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang temp.getEntry().getContactId()) 314394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy && getSpannable().getSpanStart(temp) != -1) { 3144f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy if (unfoundAddresses.contains( 3145f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy temp.getEntry().getDestination())) { 314694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy replacements.add(createFreeChip(temp.getEntry())); 314794fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } else { 314894fa301de71cc110671802eba8376c275b4055a4Scott Kennedy replacements.add(null); 314994fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } 315094fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } else { 315194fa301de71cc110671802eba8376c275b4055a4Scott Kennedy replacements.add(null); 315294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } 315394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } 315494fa301de71cc110671802eba8376c275b4055a4Scott Kennedy 315578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy processReplacements(recipients, replacements); 315694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } 315716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp }); 31581852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira return null; 31591852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 316078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 3161194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private void processReplacements(final List<DrawableRecipientChip> recipients, 3162194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final List<DrawableRecipientChip> replacements) { 316378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (replacements != null && replacements.size() > 0) { 316478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy final Runnable runnable = new Runnable() { 316578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy @Override 316678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy public void run() { 31670e128968f242866568bba0f833bb17ffda127f07Scott Kennedy final Editable text = new SpannableStringBuilder(getText()); 316878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy int i = 0; 31690e128968f242866568bba0f833bb17ffda127f07Scott Kennedy for (final DrawableRecipientChip chip : recipients) { 31700e128968f242866568bba0f833bb17ffda127f07Scott Kennedy final DrawableRecipientChip replacement = replacements.get(i); 317178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (replacement != null) { 317278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy final RecipientEntry oldEntry = chip.getEntry(); 317378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy final RecipientEntry newEntry = replacement.getEntry(); 317478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy final boolean isBetter = 317578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy RecipientAlternatesAdapter.getBetterRecipient( 317678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy oldEntry, newEntry) == newEntry; 317778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 317878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (isBetter) { 317978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // Find the location of the chip in the text currently shown. 31800e128968f242866568bba0f833bb17ffda127f07Scott Kennedy final int start = text.getSpanStart(chip); 318178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (start != -1) { 318278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // Replacing the entirety of what the chip represented, 318378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // including the extra space dividing it from other chips. 3184b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook final int end = 3185b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook Math.min(text.getSpanEnd(chip) + 1, text.length()); 31860e128968f242866568bba0f833bb17ffda127f07Scott Kennedy text.removeSpan(chip); 318778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // Make sure we always have just 1 space at the end to 318878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // separate this chip from the next chip. 31890e128968f242866568bba0f833bb17ffda127f07Scott Kennedy final SpannableString displayText = 319078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy new SpannableString(createAddressText( 31910e128968f242866568bba0f833bb17ffda127f07Scott Kennedy replacement.getEntry()).trim() + " "); 319278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy displayText.setSpan(replacement, 0, 319378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy displayText.length() - 1, 319478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 319578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // Replace the old text we found with with the new display 319678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // text, which now may also contain the display name of the 319778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy // recipient. 31980e128968f242866568bba0f833bb17ffda127f07Scott Kennedy text.replace(start, end, displayText); 319978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy replacement.setOriginalText(displayText.toString()); 320078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy replacements.set(i, null); 320178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 320278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy recipients.set(i, replacement); 320378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 320478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 320578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 320678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy i++; 320778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 32080e128968f242866568bba0f833bb17ffda127f07Scott Kennedy setText(text); 320978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 321078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy }; 321178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy 321278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy if (Looper.myLooper() == Looper.getMainLooper()) { 321378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy runnable.run(); 321478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } else { 321578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy mHandler.post(runnable); 321678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 321778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 321878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy } 32191852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 32201852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira 322178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy private class IndividualReplacementTask 3222194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy extends AsyncTask<ArrayList<DrawableRecipientChip>, Void, Void> { 32231852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira @Override 3224194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy protected Void doInBackground(ArrayList<DrawableRecipientChip>... params) { 32251852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // For each chip in the list, look up the matching contact. 32261852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // If there is a match, replace that chip with the matching 32271852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira // chip. 3228194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy final ArrayList<DrawableRecipientChip> originalRecipients = params[0]; 322903cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira ArrayList<String> addresses = new ArrayList<String>(); 3230ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao for (DrawableRecipientChip chip : originalRecipients) { 3231f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira if (chip != null) { 323203cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira addresses.add(createAddressText(chip.getEntry())); 3233f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira } 32341852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 32357a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy final BaseRecipientAdapter adapter = getAdapter(); 32368af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() { 323716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp 323816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp @Override 323994fa301de71cc110671802eba8376c275b4055a4Scott Kennedy public void matchesFound(Map<String, RecipientEntry> entries) { 3240194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy for (final DrawableRecipientChip temp : originalRecipients) { 324116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp if (RecipientEntry.isCreatedRecipient(temp.getEntry() 324216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp .getContactId()) 324316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp && getSpannable().getSpanStart(temp) != -1) { 324416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp // Replace this. 324576f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert final RecipientEntry entry = createValidatedEntry(entries 324616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp .get(tokenizeAddress(temp.getEntry().getDestination()) 324716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp .toLowerCase())); 324876f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert if (entry != null) { 324916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp mHandler.post(new Runnable() { 325016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp @Override 325116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp public void run() { 325276f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert replaceChip(temp, entry); 325316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 325416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp }); 325516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 325616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 32571852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 325816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp } 325916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp 326094fa301de71cc110671802eba8376c275b4055a4Scott Kennedy @Override 3261f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy public void matchesNotFound(final Set<String> unfoundAddresses) { 326294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy // No action required 326394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy } 326416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp }); 32651852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira return null; 32661852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 32671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira } 32681d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 3269e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira 3270e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira /** 3271e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 3272e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira * more chip across activity restarts/ 3273e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira */ 32746c2a016fb00482339dc66b93822042f9f9e2424fJin Cao private class MoreImageSpan extends ReplacementDrawableSpan { 3275e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira public MoreImageSpan(Drawable b) { 3276e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira super(b); 32776c2a016fb00482339dc66b93822042f9f9e2424fJin Cao setExtraMargin(mLineSpacingExtra); 3278e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 3279e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira } 3280e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira 32811d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 32821d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public boolean onDown(MotionEvent e) { 32831d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira return false; 32841d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 32851d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 32861d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 32871d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 32881d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira // Do nothing. 32891d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira return false; 32901d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 32911d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 32921d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 32931d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public void onLongPress(MotionEvent event) { 32941d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira if (mSelectedChip != null) { 32951d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira return; 32961d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 32971d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira float x = event.getX(); 32981d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira float y = event.getY(); 32991650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy final int offset = putOffsetInRange(x, y); 3300194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy DrawableRecipientChip currentChip = findChip(offset); 33011d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira if (currentChip != null) { 3302e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham if (mDragEnabled) { 3303e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // Start drag-and-drop for the selected chip. 3304e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham startDrag(currentChip); 3305e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } else { 3306e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // Copy the selected chip email address. 3307e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham showCopyDialog(currentChip.getEntry().getDestination()); 3308e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3309e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3310e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3311e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 33121650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy // The following methods are used to provide some functionality on older versions of Android 33131650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy // These methods were copied out of JB MR2's TextView 33141650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy ///////////////////////////////////////////////// 33151650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy private int supportGetOffsetForPosition(float x, float y) { 33161650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy if (getLayout() == null) return -1; 33171650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy final int line = supportGetLineAtCoordinate(y); 3318ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao return supportGetOffsetAtCoordinate(line, x); 33191650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 33201650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 33211650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy private float supportConvertToLocalHorizontalCoordinate(float x) { 33221650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy x -= getTotalPaddingLeft(); 33231650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy // Clamp the position to inside of the view. 33241650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy x = Math.max(0.0f, x); 33251650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 33261650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy x += getScrollX(); 33271650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy return x; 33281650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 33291650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 33301650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy private int supportGetLineAtCoordinate(float y) { 33311650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy y -= getTotalPaddingLeft(); 33321650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy // Clamp the position to inside of the view. 33331650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy y = Math.max(0.0f, y); 33341650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 33351650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy y += getScrollY(); 33361650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy return getLayout().getLineForVertical((int) y); 33371650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 33381650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 33391650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy private int supportGetOffsetAtCoordinate(int line, float x) { 33401650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy x = supportConvertToLocalHorizontalCoordinate(x); 33411650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy return getLayout().getOffsetForHorizontal(line, x); 33421650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy } 33431650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy ///////////////////////////////////////////////// 33441650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy 3345e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham /** 3346e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham * Enables drag-and-drop for chips. 3347e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham */ 3348e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham public void enableDrag() { 3349e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham mDragEnabled = true; 3350e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3351e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3352e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham /** 3353e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham * Starts drag-and-drop for the selected chip. 3354e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham */ 3355194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private void startDrag(DrawableRecipientChip currentChip) { 3356e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham String address = currentChip.getEntry().getDestination(); 3357e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA); 3358e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3359e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // Start drag mode. 3360e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham startDrag(data, new RecipientChipShadow(currentChip), null, 0); 3361e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3362e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // Remove the current chip, so drag-and-drop will result in a move. 3363e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // TODO (phamm): consider readd this chip if it's dropped outside a target. 3364e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham removeChip(currentChip); 3365e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3366e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3367e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham /** 3368e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham * Handles drag event. 3369e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham */ 3370e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham @Override 3371ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public boolean onDragEvent(@NonNull DragEvent event) { 3372e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham switch (event.getAction()) { 3373e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham case DragEvent.ACTION_DRAG_STARTED: 3374e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham // Only handle plain text drag and drop. 3375e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 3376e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham case DragEvent.ACTION_DRAG_ENTERED: 3377e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham requestFocus(); 3378e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham return true; 3379e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham case DragEvent.ACTION_DROP: 3380e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham handlePasteClip(event.getClipData()); 3381e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham return true; 3382e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3383e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham return false; 3384e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3385e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3386e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham /** 33871db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein * Drag shadow for a {@link DrawableRecipientChip}. 3388e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham */ 3389e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham private final class RecipientChipShadow extends DragShadowBuilder { 3390194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy private final DrawableRecipientChip mChip; 3391e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3392194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy public RecipientChipShadow(DrawableRecipientChip chip) { 3393e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham mChip = chip; 3394e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3395e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3396e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham @Override 3397ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public void onProvideShadowMetrics(@NonNull Point shadowSize, 3398ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao @NonNull Point shadowTouchPoint) { 3399f30a42800318f6790d55421f8f6980eb38db4d3cmindyp Rect rect = mChip.getBounds(); 3400e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham shadowSize.set(rect.width(), rect.height()); 3401e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham shadowTouchPoint.set(rect.centerX(), rect.centerY()); 3402e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham } 3403e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham 3404e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham @Override 3405ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao public void onDrawShadow(@NonNull Canvas canvas) { 3406f30a42800318f6790d55421f8f6980eb38db4d3cmindyp mChip.draw(canvas); 34071d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34081d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 34101d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira private void showCopyDialog(final String address) { 3411c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein final Context context = getContext(); 3412c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein if (!mAttachedToWindow || context == null || !(context instanceof Activity)) { 3413fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler return; 3414fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler } 3415c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein 3416c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein final DialogFragment fragment = CopyDialog.newInstance(address); 3417c6e314b5a20a234a8207ef4cd67e2f042579a087Andrew Sapperstein fragment.show(((Activity) context).getFragmentManager(), CopyDialog.TAG); 34181d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34191d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 34201d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 34211d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 34221d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira // Do nothing. 34231d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira return false; 34241d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34251d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 34261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 34271d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public void onShowPress(MotionEvent e) { 34281d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira // Do nothing. 34291d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34301d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 34311d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira @Override 34321d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira public boolean onSingleTapUp(MotionEvent e) { 34331d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira // Do nothing. 34341d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira return false; 34351d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira } 34361d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira 3437c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor protected boolean isPhoneQuery() { 3438ca5227122474fb124a4fe573a2f07e14d7a33f3eMindy Pereira return getAdapter() != null 3439858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy && getAdapter().getQueryType() == BaseRecipientAdapter.QUERY_TYPE_PHONE; 3440858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy } 3441858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy 3442858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy @Override 3443858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy public BaseRecipientAdapter getAdapter() { 3444858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy return (BaseRecipientAdapter) super.getAdapter(); 3445c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor } 3446972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang 3447972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang /** 3448972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang * Append a new {@link RecipientEntry} to the end of the recipient chips, leaving any 3449972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang * unfinished text at the end. 3450972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang */ 3451972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang public void appendRecipientEntry(final RecipientEntry entry) { 3452972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang clearComposingText(); 3453972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang 3454972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang final Editable editable = getText(); 3455972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang int chipInsertionPoint = 0; 3456972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang 3457972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang // Find the end of last chip and see if there's any unchipified text. 3458972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang final DrawableRecipientChip[] recips = getSortedRecipients(); 3459972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang if (recips != null && recips.length > 0) { 3460972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang final DrawableRecipientChip last = recips[recips.length - 1]; 3461972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang // The chip will be inserted at the end of last chip + 1. All the unfinished text after 3462972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang // the insertion point will be kept untouched. 3463972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang chipInsertionPoint = editable.getSpanEnd(last) + 1; 3464972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang } 3465972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang 3466ffc0111fa742c72f2457cecc1116e4ea3c6c8c6eJin Cao final CharSequence chip = createChip(entry); 3467972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang if (chip != null) { 3468972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang editable.insert(chipInsertionPoint, chip); 3469972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang } 3470972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang } 347135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao 3472cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang /** 3473cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang * Remove all chips matching the given RecipientEntry. 3474cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang */ 3475cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang public void removeRecipientEntry(final RecipientEntry entry) { 3476cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang final DrawableRecipientChip[] recips = getText() 3477cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang .getSpans(0, getText().length(), DrawableRecipientChip.class); 3478cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang 3479cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang for (final DrawableRecipientChip recipient : recips) { 3480cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang final RecipientEntry existingEntry = recipient.getEntry(); 3481cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang if (existingEntry != null && existingEntry.isValid() && 3482cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang existingEntry.isSamePerson(entry)) { 3483cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang removeChip(recipient); 3484cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang } 3485cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang } 3486cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang } 3487cd980d9f73bfa02155f7bb092ae1fb212ed6e8e2Kaikai Wang 34884de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao public void setAlternatePopupAnchor(View v) { 34894de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao mAlternatePopupAnchor = v; 34904de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao } 34914de6a53de8959dc20bfb05e7b54cb47ccc5367ecJin Cao 3492654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy @Override 3493654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy public void setVisibility(int visibility) { 3494654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy super.setVisibility(visibility); 3495654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy 3496654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy if (visibility != GONE && mRequiresShrinkWhenNotGone) { 3497654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy mRequiresShrinkWhenNotGone = false; 3498654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy mHandler.post(mDelayedShrink); 3499654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy } 3500654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy } 3501654c967e9560ccb40a465a78e3296980f530ed55Scott Kennedy 350235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao private static class ChipBitmapContainer { 350335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao Bitmap bitmap; 350435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao // information used for positioning the loaded icon 350535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao boolean loadIcon = true; 350635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao float left; 350735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao float top; 350835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao float right; 350935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao float bottom; 3510e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam // information used for positioning the warning icon 3511e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam float warningIconLeft; 3512e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam float warningIconTop; 3513e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam float warningIconRight; 3514e2348bfcd0a710201ba98cd5642cc1e3b5fa6465Joseph Moghadam float warningIconBottom; 351535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao } 35161852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira} 3517