RecipientsEditor.java revision ba7455b4de3c892aed830763ca80d22c8c940e78
172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project/* 272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * Copyright (C) 2008 Esmertec AG. 372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * Copyright (C) 2008 The Android Open Source Project 472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * 572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * you may not use this file except in compliance with the License. 772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * You may obtain a copy of the License at 872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * 972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 1072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * 1172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software 1272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 1372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * See the License for the specific language governing permissions and 1572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * limitations under the License. 1672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project */ 1772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 1872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectpackage com.android.mms.ui; 1972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 201d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport com.android.mms.MmsConfig; 211d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport com.android.mms.data.Contact; 221d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport com.android.mms.data.ContactList; 2372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 2472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.content.Context; 25f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony.Mms; 261d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport android.telephony.PhoneNumberUtils; 2772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.text.Annotation; 282f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Projectimport android.text.Editable; 2972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.text.Layout; 301d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport android.text.Spannable; 311d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport android.text.SpannableString; 3272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.text.SpannableStringBuilder; 3372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.text.Spanned; 3472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.text.TextUtils; 352f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Projectimport android.text.TextWatcher; 3672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.util.AttributeSet; 373f1a2ade1338a719ceb0fff053951577a21ebec4Amith Yamasaniimport android.view.inputmethod.EditorInfo; 3872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.view.MotionEvent; 3972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.view.ContextMenu.ContextMenuInfo; 4072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectimport android.widget.MultiAutoCompleteTextView; 4172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 421d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport java.util.ArrayList; 431d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrickimport java.util.List; 4472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 4572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project/** 4672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project * Provide UI for editing the recipients of multi-media messages. 4772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project */ 4872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Projectpublic class RecipientsEditor extends MultiAutoCompleteTextView { 4972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project private int mLongPressedPosition = -1; 5072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project private final RecipientsEditorTokenizer mTokenizer; 51b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor private char mLastSeparator = ','; 5272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 5372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project public RecipientsEditor(Context context, AttributeSet attrs) { 5472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project super(context, attrs, android.R.attr.autoCompleteTextViewStyle); 55ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev mTokenizer = new RecipientsEditorTokenizer(); 5672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project setTokenizer(mTokenizer); 573f1a2ade1338a719ceb0fff053951577a21ebec4Amith Yamasani // For the focus to move to the message body when soft Next is pressed 583f1a2ade1338a719ceb0fff053951577a21ebec4Amith Yamasani setImeOptions(EditorInfo.IME_ACTION_NEXT); 592f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project 60c7797433b1641be0cb77e02541b8bfed94c48835Tom Taylor setThreshold(1); // pop-up the list after a single char is typed 61c7797433b1641be0cb77e02541b8bfed94c48835Tom Taylor 622f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project /* 632f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * The point of this TextWatcher is that when the user chooses 642f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * an address completion from the AutoCompleteTextView menu, it 652f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * is marked up with Annotation objects to tie it back to the 662f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * address book entry that it came from. If the user then goes 672f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * back and edits that part of the text, it no longer corresponds 682f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * to that address book entry and needs to have the Annotations 692f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project * claiming that it does removed. 702f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project */ 712f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project addTextChangedListener(new TextWatcher() { 722f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project private Annotation[] mAffected; 732f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project 74ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 752f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project public void beforeTextChanged(CharSequence s, int start, 76592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int count, int after) { 772f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project mAffected = ((Spanned) s).getSpans(start, start + count, 78592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor Annotation.class); 792f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project } 802f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project 81ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 822f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project public void onTextChanged(CharSequence s, int start, 83592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int before, int after) { 84592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if (before == 0 && after == 1) { // inserting a character 85592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor char c = s.charAt(start); 86592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if (c == ',' || c == ';') { 87592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor // Remember the delimiter the user typed to end this recipient. We'll 88592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor // need it shortly in terminateToken(). 89592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor mLastSeparator = c; 90592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 91592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 922f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project } 932f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project 94ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 952f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project public void afterTextChanged(Editable s) { 962f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project if (mAffected != null) { 972f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project for (Annotation a : mAffected) { 982f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project s.removeSpan(a); 992f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project } 1002f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project } 1012f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project mAffected = null; 1022f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project } 1032f56791329cefc49ce79663b8d3aff50594cc1f3The Android Open Source Project }); 10472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 105592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 1064a289bdc49453d22851ce7a46a893604536a539dTom Taylor @Override 1074a289bdc49453d22851ce7a46a893604536a539dTom Taylor public boolean enoughToFilter() { 1085d31dfbc15ca3403b8c0a489256fc1e7fba38101Tom Taylor if (!super.enoughToFilter()) { 1095d31dfbc15ca3403b8c0a489256fc1e7fba38101Tom Taylor return false; 1105d31dfbc15ca3403b8c0a489256fc1e7fba38101Tom Taylor } 111592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor // If the user is in the middle of editing an existing recipient, don't offer the 1124a289bdc49453d22851ce7a46a893604536a539dTom Taylor // auto-complete menu. Without this, when the user selects an auto-complete menu item, 1134a289bdc49453d22851ce7a46a893604536a539dTom Taylor // it will get added to the list of recipients so we end up with the old before-editing 1144a289bdc49453d22851ce7a46a893604536a539dTom Taylor // recipient and the new post-editing recipient. As a precedent, gmail does not show 1154a289bdc49453d22851ce7a46a893604536a539dTom Taylor // the auto-complete menu when editing an existing recipient. 1164a289bdc49453d22851ce7a46a893604536a539dTom Taylor int end = getSelectionEnd(); 1174a289bdc49453d22851ce7a46a893604536a539dTom Taylor int len = getText().length(); 1184a289bdc49453d22851ce7a46a893604536a539dTom Taylor 1194a289bdc49453d22851ce7a46a893604536a539dTom Taylor return end == len; 1205d31dfbc15ca3403b8c0a489256fc1e7fba38101Tom Taylor 1214a289bdc49453d22851ce7a46a893604536a539dTom Taylor } 12272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 1231d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public int getRecipientCount() { 1241d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return mTokenizer.getNumbers().size(); 12572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 12672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 1271d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public List<String> getNumbers() { 1281d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return mTokenizer.getNumbers(); 1291d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1301d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 131a6dd14dc0963aa8f30e625aa88e925e3fa0744f0Bai Tao public ContactList constructContactsFromInput(boolean blocking) { 1321d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick List<String> numbers = mTokenizer.getNumbers(); 1331d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick ContactList list = new ContactList(); 1341d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (String number : numbers) { 135a6dd14dc0963aa8f30e625aa88e925e3fa0744f0Bai Tao Contact contact = Contact.get(number, blocking); 13625b939e5a7ecb1e0879b684dc5bc55183cf468b4Wei Huang contact.setNumber(number); 13725b939e5a7ecb1e0879b684dc5bc55183cf468b4Wei Huang list.add(contact); 1381d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1391d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return list; 1401d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1411d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 14278f81825431c8c52cad57bc4a363747533a701abWei Huang private boolean isValidAddress(String number, boolean isMms) { 14378f81825431c8c52cad57bc4a363747533a701abWei Huang if (isMms) { 14478f81825431c8c52cad57bc4a363747533a701abWei Huang return MessageUtils.isValidMmsAddress(number); 14578f81825431c8c52cad57bc4a363747533a701abWei Huang } else { 14649183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang // TODO: PhoneNumberUtils.isWellFormedSmsAddress() only check if the number is a valid 14749183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang // GSM SMS address. If the address contains a dialable char, it considers it a well 14849183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang // formed SMS addr. CDMA doesn't work that way and has a different parser for SMS 14949183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang // address (see CdmaSmsAddress.parse(String address)). We should definitely fix this!!! 15049183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang return PhoneNumberUtils.isWellFormedSmsAddress(number) 15149183c099b1ac79f3c2fe97368ed76caba4ca98eWei Huang || Mms.isEmailAddress(number); 1521d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1531d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1541d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 15578f81825431c8c52cad57bc4a363747533a701abWei Huang public boolean hasValidRecipient(boolean isMms) { 1561d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (String number : mTokenizer.getNumbers()) { 15778f81825431c8c52cad57bc4a363747533a701abWei Huang if (isValidAddress(number, isMms)) 1581d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return true; 1591d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1601d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return false; 1611d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1621d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 16378f81825431c8c52cad57bc4a363747533a701abWei Huang public boolean hasInvalidRecipient(boolean isMms) { 1641d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (String number : mTokenizer.getNumbers()) { 16578f81825431c8c52cad57bc4a363747533a701abWei Huang if (!isValidAddress(number, isMms)) { 166817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom if (MmsConfig.getEmailGateway() == null) { 167817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom return true; 168817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom } else if (!MessageUtils.isAlias(number)) { 169817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom return true; 170817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom } 171817eb982a66303a8f87fc3061e3493a232627e96Satish Roddom } 1721d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1731d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return false; 1741d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1751d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 17678f81825431c8c52cad57bc4a363747533a701abWei Huang public String formatInvalidNumbers(boolean isMms) { 1771d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick StringBuilder sb = new StringBuilder(); 1781d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (String number : mTokenizer.getNumbers()) { 17978f81825431c8c52cad57bc4a363747533a701abWei Huang if (!isValidAddress(number, isMms)) { 1801d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick if (sb.length() != 0) { 1811d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick sb.append(", "); 1821d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1831d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick sb.append(number); 1841d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1851d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1861d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return sb.toString(); 1871d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1881d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 1891d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public boolean containsEmail() { 1901d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick if (TextUtils.indexOf(getText(), '@') == -1) 1911d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return false; 1921d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 1931d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick List<String> numbers = mTokenizer.getNumbers(); 1941d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (String number : numbers) { 1951d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick if (Mms.isEmailAddress(number)) 1961d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return true; 1971d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 1981d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return false; 1991d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 2001d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 2011d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public static CharSequence contactToToken(Contact c) { 2021d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick SpannableString s = new SpannableString(c.getNameAndNumber()); 2031d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick int len = s.length(); 2041d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 2051d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick if (len == 0) { 2061d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return s; 2071d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 2081d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 2091d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick s.setSpan(new Annotation("number", c.getNumber()), 0, len, 2101d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 2111d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 2121d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return s; 2131d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick } 2141d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick 2151d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public void populate(ContactList list) { 21672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project SpannableStringBuilder sb = new SpannableStringBuilder(); 21772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 2182ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // Very tricky bug. In the recipient editor, we always leave a trailing 2192ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // comma to make it easy for users to add additional recipients. When a 2202ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // user types (or chooses from the dropdown) a new contact Mms has never 2212ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // seen before, the contact gets the correct trailing comma. But when the 2222ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // contact gets added to the mms's contacts table, contacts sends out an 2232ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // onUpdate to CMA. CMA would recompute the recipients and since the 2242ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // recipient editor was still visible, call mRecipientsEditor.populate(recipients). 2252ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // This would replace the recipient that had a comma with a recipient 2262ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // without a comma. When a user manually added a new comma to add another 2272ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // recipient, this would eliminate the span inside the text. The span contains the 2282ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // number part of "Fred Flinstone <123-1231>". Hence, the whole 2292ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // "Fred Flinstone <123-1231>" would be considered the number of 2302ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // the first recipient and get entered into the canonical_addresses table. 2312ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // The fix for this particular problem is very easy. All recipients have commas. 2322ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // TODO: However, the root problem remains. If a user enters the recipients editor 2332ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // and deletes chars into an address chosen from the suggestions, it'll cause 2342ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // the number annotation to get deleted and the whole address (name + number) will 2352ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor // be used as the number. 2361d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick for (Contact c : list) { 2372ddc2c0585fec6fca59acc115e26dc51f69aacc5Tom Taylor sb.append(contactToToken(c)).append(", "); 23872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 23972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 24072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project setText(sb); 24172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 24272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 24372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project private int pointToPosition(int x, int y) { 24472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project x -= getCompoundPaddingLeft(); 24572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project y -= getExtendedPaddingTop(); 24672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 24772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 24872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project x += getScrollX(); 24972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project y += getScrollY(); 25072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 25172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project Layout layout = getLayout(); 25272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project if (layout == null) { 25372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return -1; 25472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 25572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 25672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project int line = layout.getLineForVertical(y); 25772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project int off = layout.getOffsetForHorizontal(line, x); 25872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 25972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return off; 26072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 26172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 26272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project @Override 26372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project public boolean onTouchEvent(MotionEvent ev) { 26472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project final int action = ev.getAction(); 26572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project final int x = (int) ev.getX(); 26672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project final int y = (int) ev.getY(); 26772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 26872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project if (action == MotionEvent.ACTION_DOWN) { 26972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project mLongPressedPosition = pointToPosition(x, y); 27072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 27172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 27272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return super.onTouchEvent(ev); 27372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 27472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 27572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project @Override 27672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project protected ContextMenuInfo getContextMenuInfo() { 27772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project if ((mLongPressedPosition >= 0)) { 27872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project Spanned text = getText(); 2793d2107af884e95720b72201e99d9ab4bce9bba3bTom Taylor if (mLongPressedPosition <= text.length()) { 2803d2107af884e95720b72201e99d9ab4bce9bba3bTom Taylor int start = mTokenizer.findTokenStart(text, mLongPressedPosition); 2813d2107af884e95720b72201e99d9ab4bce9bba3bTom Taylor int end = mTokenizer.findTokenEnd(text, start); 28272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 2833d2107af884e95720b72201e99d9ab4bce9bba3bTom Taylor if (end != start) { 2846be18bedb5b87dbbcdb54f37d5a0945bd0f71377Tom Taylor String number = getNumberAt(getText(), start, end, getContext()); 285715e32f97bd9d8ce4b5ba650b97ba4b137150456Tom Taylor Contact c = Contact.get(number, false); 2861d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return new RecipientContextMenuInfo(c); 2873d2107af884e95720b72201e99d9ab4bce9bba3bTom Taylor } 28872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 28972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 29072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return null; 29172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 29272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 2931d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick private static String getNumberAt(Spanned sp, int start, int end, Context context) { 294b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor return getFieldAt("number", sp, start, end, context); 295b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor } 296b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor 297b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor private static int getSpanLength(Spanned sp, int start, int end, Context context) { 298b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // TODO: there's a situation where the span can lose its annotations: 299b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // - add an auto-complete contact 300b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // - add another auto-complete contact 301b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // - delete that second contact and keep deleting into the first 302b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // - we lose the annotation and can no longer get the span. 303b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // Need to fix this case because it breaks auto-complete contacts with commas in the name. 30472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project Annotation[] a = sp.getSpans(start, end, Annotation.class); 305b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor if (a.length > 0) { 306b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor return sp.getSpanEnd(a[0]); 307836945de572ac80fd6f6a7bf59036d4cce801410The Android Open Source Project } 308b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor return 0; 309b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor } 310b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor 311b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor private static String getFieldAt(String field, Spanned sp, int start, int end, 312b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor Context context) { 313b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor Annotation[] a = sp.getSpans(start, end, Annotation.class); 314b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor String fieldValue = getAnnotation(a, field); 315b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor if (TextUtils.isEmpty(fieldValue)) { 316b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor fieldValue = TextUtils.substring(sp, start, end); 317b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor } 318b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor return fieldValue; 319592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 32072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 32172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 32272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project private static String getAnnotation(Annotation[] a, String key) { 32372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project for (int i = 0; i < a.length; i++) { 32472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project if (a[i].getKey().equals(key)) { 32572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return a[i].getValue(); 32672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 32772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 32872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 32972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project return ""; 33072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 33172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 33272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project private class RecipientsEditorTokenizer 33372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project implements MultiAutoCompleteTextView.Tokenizer { 33472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 335ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 336592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor public int findTokenStart(CharSequence text, int cursor) { 337592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int i = cursor; 338592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor char c; 339592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 340592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor while (i > 0 && (c = text.charAt(i - 1)) != ',' && c != ';') { 341592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor i--; 342592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 343592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor while (i < cursor && text.charAt(i) == ' ') { 344592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor i++; 345592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 346592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 347592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return i; 348592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 349592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 350ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 351592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor public int findTokenEnd(CharSequence text, int cursor) { 352592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int i = cursor; 353592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int len = text.length(); 354592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor char c; 355592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 356592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor while (i < len) { 357592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if ((c = text.charAt(i)) == ',' || c == ';') { 358592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return i; 359592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } else { 360592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor i++; 361592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 362592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 363592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 364592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return len; 365592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 366592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 367ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev @Override 368592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor public CharSequence terminateToken(CharSequence text) { 369592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor int i = text.length(); 370592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 371592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor while (i > 0 && text.charAt(i - 1) == ' ') { 372592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor i--; 373592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 374592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 375592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor char c; 376592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if (i > 0 && ((c = text.charAt(i - 1)) == ',' || c == ';')) { 377592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return text; 378592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } else { 379592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor // Use the same delimiter the user just typed. 380592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor // This lets them have a mixture of commas and semicolons in their list. 381592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor String separator = mLastSeparator + " "; 382592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if (text instanceof Spanned) { 383592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor SpannableString sp = new SpannableString(text + separator); 384592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor TextUtils.copySpansFrom((Spanned) text, 0, text.length(), 385592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor Object.class, sp, 0); 386592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return sp; 387592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } else { 388592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor return text + separator; 389592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 390592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 391592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor } 392592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor 3931d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick public List<String> getNumbers() { 394ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev Spanned sp = RecipientsEditor.this.getText(); 39572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project int len = sp.length(); 3961d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick List<String> list = new ArrayList<String>(); 3974a289bdc49453d22851ce7a46a893604536a539dTom Taylor 39872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project int start = 0; 39972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project int i = 0; 40072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project while (i < len + 1) { 401592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor char c; 402592a22133822eaa0f54a1de1d56cea14f57c2677Tom Taylor if ((i == len) || ((c = sp.charAt(i)) == ',') || (c == ';')) { 40372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project if (i > start) { 404ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev list.add(getNumberAt(sp, start, i, getContext())); 405b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor 406b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // calculate the recipients total length. This is so if the name contains 407b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // commas or semis, we'll skip over the whole name to the next 408b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // recipient, rather than parsing this single name into multiple 409b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor // recipients. 410ba7455b4de3c892aed830763ca80d22c8c940e78Todor Kalaydjiev int spanLen = getSpanLength(sp, start, i, getContext()); 411b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor if (spanLen > i) { 412b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor i = spanLen; 413b8bd3d53720ee9ebf9146a1d515a516c2068ff69Tom Taylor } 41472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 41572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 41672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project i++; 41772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 41872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project while ((i < len) && (sp.charAt(i) == ' ')) { 41972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project i++; 42072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 42172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 42272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project start = i; 42372735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } else { 42472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project i++; 42572735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 42672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 42772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 4281d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick return list; 42972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 43072735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 43172735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 43272735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project static class RecipientContextMenuInfo implements ContextMenuInfo { 4331d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick final Contact recipient; 43472735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project 4351d98ae0b203e01034ddead4214d1520ce863a23bFicus Kirkpatrick RecipientContextMenuInfo(Contact r) { 43672735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project recipient = r; 43772735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 43872735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project } 43972735c62aba8fd2a9420a0f9f83d22543e3c164fThe Android Open Source Project} 440