/*
* 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;
}
}