| /* |
| * Copyright (C) 2008 Esmertec AG. |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mms.ui; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.MatrixCursor; |
| import android.database.MergeCursor; |
| import android.net.Uri; |
| import android.provider.ContactsContract.CommonDataKinds.Phone; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.DataUsageFeedback; |
| import android.telephony.PhoneNumberUtils; |
| import android.text.Annotation; |
| import android.text.Spannable; |
| import android.text.SpannableString; |
| import android.text.TextUtils; |
| import android.view.View; |
| import android.widget.ResourceCursorAdapter; |
| import android.widget.TextView; |
| |
| import com.android.mms.MmsApp; |
| import com.android.mms.R; |
| import com.android.mms.data.Contact; |
| |
| /** |
| * This adapter is used to filter contacts on both name and number. |
| */ |
| public class RecipientsAdapter extends ResourceCursorAdapter { |
| |
| public static final int CONTACT_ID_INDEX = 1; |
| public static final int TYPE_INDEX = 2; |
| public static final int NUMBER_INDEX = 3; |
| public static final int LABEL_INDEX = 4; |
| public static final int NAME_INDEX = 5; |
| public static final int NORMALIZED_NUMBER = 6; |
| |
| private static final String[] PROJECTION_PHONE = { |
| Phone._ID, // 0 |
| Phone.CONTACT_ID, // 1 |
| Phone.TYPE, // 2 |
| Phone.NUMBER, // 3 |
| Phone.LABEL, // 4 |
| Phone.DISPLAY_NAME, // 5 |
| Phone.NORMALIZED_NUMBER, // 6 |
| }; |
| |
| private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," |
| + Contacts.DISPLAY_NAME + "," + Phone.TYPE; |
| |
| private final Context mContext; |
| private final ContentResolver mContentResolver; |
| private final String mDefaultCountryIso; |
| |
| public RecipientsAdapter(Context context) { |
| // Note that the RecipientsAdapter doesn't support auto-requeries. If we |
| // want to respond to changes in the contacts we're displaying in the drop-down, |
| // code using this adapter would have to add a line such as: |
| // mRecipientsAdapter.setOnDataSetChangedListener(mDataSetChangedListener); |
| // See ComposeMessageActivity for an example. |
| super(context, R.layout.recipient_filter_item, null, false /* no auto-requery */); |
| mContext = context; |
| mContentResolver = context.getContentResolver(); |
| mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); |
| } |
| |
| @Override |
| public final CharSequence convertToString(Cursor cursor) { |
| String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX); |
| if (number == null) { |
| return ""; |
| } |
| number = number.trim(); |
| |
| String name = cursor.getString(RecipientsAdapter.NAME_INDEX); |
| int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX); |
| |
| String label = cursor.getString(RecipientsAdapter.LABEL_INDEX); |
| CharSequence displayLabel = Phone.getDisplayLabel(mContext, type, label); |
| |
| if (name == null) { |
| name = ""; |
| } else { |
| // Names with commas are the bane of the recipient editor's existence. |
| // We've worked around them by using spans, but there are edge cases |
| // where the spans get deleted. Furthermore, having commas in names |
| // can be confusing to the user since commas are used as separators |
| // between recipients. The best solution is to simply remove commas |
| // from names. |
| name = name.replace(", ", " ") |
| .replace(",", " "); // Make sure we leave a space between parts of names. |
| } |
| |
| String nameAndNumber = Contact.formatNameAndNumber( name, number, |
| cursor.getString(NORMALIZED_NUMBER)); |
| |
| SpannableString out = new SpannableString(nameAndNumber); |
| int len = out.length(); |
| |
| if (!TextUtils.isEmpty(name)) { |
| out.setSpan(new Annotation("name", name), 0, len, |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| } else { |
| out.setSpan(new Annotation("name", number), 0, len, |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| } |
| |
| String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX); |
| out.setSpan(new Annotation("person_id", person_id), 0, len, |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| out.setSpan(new Annotation("label", displayLabel.toString()), 0, len, |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| out.setSpan(new Annotation("number", number), 0, len, |
| Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| |
| return out; |
| } |
| |
| @Override |
| public final void bindView(View view, Context context, Cursor cursor) { |
| TextView name = (TextView) view.findViewById(R.id.name); |
| name.setText(cursor.getString(NAME_INDEX)); |
| |
| TextView label = (TextView) view.findViewById(R.id.label); |
| int type = cursor.getInt(TYPE_INDEX); |
| CharSequence labelText = Phone.getDisplayLabel(mContext, type, |
| cursor.getString(LABEL_INDEX)); |
| // When there's no label, getDisplayLabel() returns a CharSequence of length==1 containing |
| // a unicode non-breaking space. Need to check for that and consider that as "no label". |
| if (labelText.length() == 0 || |
| (labelText.length() == 1 && labelText.charAt(0) == '\u00A0')) { |
| label.setVisibility(View.GONE); |
| } else { |
| label.setText(labelText); |
| label.setVisibility(View.VISIBLE); |
| } |
| |
| TextView number = (TextView) view.findViewById(R.id.number); |
| number.setText( |
| PhoneNumberUtils.formatNumber(cursor.getString(NUMBER_INDEX), |
| cursor.getString(NORMALIZED_NUMBER), mDefaultCountryIso)); |
| } |
| |
| @Override |
| public Cursor runQueryOnBackgroundThread(CharSequence constraint) { |
| String phone = ""; |
| String cons = null; |
| |
| if (constraint != null) { |
| cons = constraint.toString(); |
| |
| if (usefulAsDigits(cons)) { |
| phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons); |
| if (phone.equals(cons)) { |
| phone = ""; |
| } else { |
| phone = phone.trim(); |
| } |
| } |
| } |
| |
| Uri uri = Phone.CONTENT_FILTER_URI.buildUpon() |
| .appendPath(cons) |
| .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, |
| DataUsageFeedback.USAGE_TYPE_SHORT_TEXT) |
| .build(); |
| /* |
| * if we decide to filter based on phone types use a selection |
| * like this. |
| String selection = String.format("%s=%s OR %s=%s OR %s=%s", |
| Phone.TYPE, |
| Phone.TYPE_MOBILE, |
| Phone.TYPE, |
| Phone.TYPE_WORK_MOBILE, |
| Phone.TYPE, |
| Phone.TYPE_MMS); |
| */ |
| Cursor phoneCursor = |
| mContentResolver.query(uri, |
| PROJECTION_PHONE, |
| null, //selection, |
| null, |
| null); |
| |
| if (phone.length() > 0) { |
| Object[] result = new Object[7]; |
| result[0] = Integer.valueOf(-1); // ID |
| result[1] = Long.valueOf(-1); // CONTACT_ID |
| result[2] = Integer.valueOf(Phone.TYPE_CUSTOM); // TYPE |
| result[3] = phone; // NUMBER |
| |
| /* |
| * The "\u00A0" keeps Phone.getDisplayLabel() from deciding |
| * to display the default label ("Home") next to the transformation |
| * of the letters into numbers. |
| */ |
| result[4] = "\u00A0"; // LABEL |
| result[5] = cons; // NAME |
| result[6] = phone; // NORMALIZED_NUMBER |
| |
| MatrixCursor translated = new MatrixCursor(PROJECTION_PHONE, 1); |
| translated.addRow(result); |
| return new MergeCursor(new Cursor[] { translated, phoneCursor }); |
| } else { |
| return phoneCursor; |
| } |
| } |
| |
| /** |
| * Returns true if all the characters are meaningful as digits |
| * in a phone number -- letters, digits, and a few punctuation marks. |
| */ |
| private boolean usefulAsDigits(CharSequence cons) { |
| int len = cons.length(); |
| |
| for (int i = 0; i < len; i++) { |
| char c = cons.charAt(i); |
| |
| if ((c >= '0') && (c <= '9')) { |
| continue; |
| } |
| if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+') |
| || (c == '#') || (c == '*')) { |
| continue; |
| } |
| if ((c >= 'A') && (c <= 'Z')) { |
| continue; |
| } |
| if ((c >= 'a') && (c <= 'z')) { |
| continue; |
| } |
| |
| return false; |
| } |
| |
| return true; |
| } |
| } |