1 /* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mms.ui; 19 20 import com.android.common.ArrayListCursor; 21 import com.android.mms.R; 22 import com.android.mms.data.Contact; 23 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.database.Cursor; 27 import android.database.MergeCursor; 28 import android.net.Uri; 29 import android.provider.ContactsContract.Contacts; 30 import android.provider.ContactsContract.CommonDataKinds.Phone; 31 import android.telephony.PhoneNumberUtils; 32 import android.text.Annotation; 33 import android.text.Spannable; 34 import android.text.SpannableString; 35 import android.text.TextUtils; 36 import android.view.View; 37 import android.widget.ResourceCursorAdapter; 38 import android.widget.TextView; 39 40 import java.util.ArrayList; 41 42 /** 43 * This adapter is used to filter contacts on both name and number. 44 */ 45 public class RecipientsAdapter extends ResourceCursorAdapter { 46 47 public static final int CONTACT_ID_INDEX = 1; 48 public static final int TYPE_INDEX = 2; 49 public static final int NUMBER_INDEX = 3; 50 public static final int LABEL_INDEX = 4; 51 public static final int NAME_INDEX = 5; 52 53 private static final String[] PROJECTION_PHONE = { 54 Phone._ID, // 0 55 Phone.CONTACT_ID, // 1 56 Phone.TYPE, // 2 57 Phone.NUMBER, // 3 58 Phone.LABEL, // 4 59 Phone.DISPLAY_NAME, // 5 60 }; 61 62 private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," 63 + Contacts.DISPLAY_NAME + "," + Phone.TYPE; 64 65 private final Context mContext; 66 private final ContentResolver mContentResolver; 67 RecipientsAdapter(Context context)68 public RecipientsAdapter(Context context) { 69 // Note that the RecipientsAdapter doesn't support auto-requeries. If we 70 // want to respond to changes in the contacts we're displaying in the drop-down, 71 // code using this adapter would have to add a line such as: 72 // mRecipientsAdapter.setOnDataSetChangedListener(mDataSetChangedListener); 73 // See ComposeMessageActivity for an example. 74 super(context, R.layout.recipient_filter_item, null, false /* no auto-requery */); 75 mContext = context; 76 mContentResolver = context.getContentResolver(); 77 } 78 79 @Override convertToString(Cursor cursor)80 public final CharSequence convertToString(Cursor cursor) { 81 String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX); 82 if (number == null) { 83 return ""; 84 } 85 number = number.trim(); 86 87 String name = cursor.getString(RecipientsAdapter.NAME_INDEX); 88 int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX); 89 90 String label = cursor.getString(RecipientsAdapter.LABEL_INDEX); 91 CharSequence displayLabel = Phone.getDisplayLabel(mContext, type, label); 92 93 if (name == null) { 94 name = ""; 95 } else { 96 // Names with commas are the bane of the recipient editor's existence. 97 // We've worked around them by using spans, but there are edge cases 98 // where the spans get deleted. Furthermore, having commas in names 99 // can be confusing to the user since commas are used as separators 100 // between recipients. The best solution is to simply remove commas 101 // from names. 102 name = name.replace(", ", " ") 103 .replace(",", " "); // Make sure we leave a space between parts of names. 104 } 105 106 String nameAndNumber = Contact.formatNameAndNumber(name, number); 107 108 SpannableString out = new SpannableString(nameAndNumber); 109 int len = out.length(); 110 111 if (!TextUtils.isEmpty(name)) { 112 out.setSpan(new Annotation("name", name), 0, len, 113 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 114 } else { 115 out.setSpan(new Annotation("name", number), 0, len, 116 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 117 } 118 119 String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX); 120 out.setSpan(new Annotation("person_id", person_id), 0, len, 121 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 122 out.setSpan(new Annotation("label", displayLabel.toString()), 0, len, 123 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 124 out.setSpan(new Annotation("number", number), 0, len, 125 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 126 127 return out; 128 } 129 130 @Override bindView(View view, Context context, Cursor cursor)131 public final void bindView(View view, Context context, Cursor cursor) { 132 TextView name = (TextView) view.findViewById(R.id.name); 133 name.setText(cursor.getString(NAME_INDEX)); 134 135 TextView label = (TextView) view.findViewById(R.id.label); 136 int type = cursor.getInt(TYPE_INDEX); 137 CharSequence labelText = Phone.getDisplayLabel(mContext, type, 138 cursor.getString(LABEL_INDEX)); 139 // When there's no label, getDisplayLabel() returns a CharSequence of length==1 containing 140 // a unicode non-breaking space. Need to check for that and consider that as "no label". 141 if (labelText.length() == 0 || 142 (labelText.length() == 1 && labelText.charAt(0) == '\u00A0')) { 143 label.setVisibility(View.GONE); 144 } else { 145 label.setText(labelText); 146 label.setVisibility(View.VISIBLE); 147 } 148 149 TextView number = (TextView) view.findViewById(R.id.number); 150 number.setText(cursor.getString(NUMBER_INDEX)); 151 } 152 153 @Override runQueryOnBackgroundThread(CharSequence constraint)154 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 155 String phone = ""; 156 String cons = null; 157 158 if (constraint != null) { 159 cons = constraint.toString(); 160 161 if (usefulAsDigits(cons)) { 162 phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons); 163 if (phone.equals(cons)) { 164 phone = ""; 165 } else { 166 phone = phone.trim(); 167 } 168 } 169 } 170 171 Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons)); 172 /* 173 * if we decide to filter based on phone types use a selection 174 * like this. 175 String selection = String.format("%s=%s OR %s=%s OR %s=%s", 176 Phone.TYPE, 177 Phone.TYPE_MOBILE, 178 Phone.TYPE, 179 Phone.TYPE_WORK_MOBILE, 180 Phone.TYPE, 181 Phone.TYPE_MMS); 182 */ 183 Cursor phoneCursor = 184 mContentResolver.query(uri, 185 PROJECTION_PHONE, 186 null, //selection, 187 null, 188 SORT_ORDER); 189 190 if (phone.length() > 0) { 191 ArrayList result = new ArrayList(); 192 result.add(Integer.valueOf(-1)); // ID 193 result.add(Long.valueOf(-1)); // CONTACT_ID 194 result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE 195 result.add(phone); // NUMBER 196 197 /* 198 * The "\u00A0" keeps Phone.getDisplayLabel() from deciding 199 * to display the default label ("Home") next to the transformation 200 * of the letters into numbers. 201 */ 202 result.add("\u00A0"); // LABEL 203 result.add(cons); // NAME 204 205 ArrayList<ArrayList> wrap = new ArrayList<ArrayList>(); 206 wrap.add(result); 207 208 ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap); 209 210 return new MergeCursor(new Cursor[] { translated, phoneCursor }); 211 } else { 212 return phoneCursor; 213 } 214 } 215 216 /** 217 * Returns true if all the characters are meaningful as digits 218 * in a phone number -- letters, digits, and a few punctuation marks. 219 */ usefulAsDigits(CharSequence cons)220 private boolean usefulAsDigits(CharSequence cons) { 221 int len = cons.length(); 222 223 for (int i = 0; i < len; i++) { 224 char c = cons.charAt(i); 225 226 if ((c >= '0') && (c <= '9')) { 227 continue; 228 } 229 if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+') 230 || (c == '#') || (c == '*')) { 231 continue; 232 } 233 if ((c >= 'A') && (c <= 'Z')) { 234 continue; 235 } 236 if ((c >= 'a') && (c <= 'z')) { 237 continue; 238 } 239 240 return false; 241 } 242 243 return true; 244 } 245 } 246