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.mms.MmsApp; 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.MatrixCursor; 28 import android.database.MergeCursor; 29 import android.net.Uri; 30 import android.provider.ContactsContract.Contacts; 31 import android.provider.ContactsContract.CommonDataKinds.Phone; 32 import android.provider.ContactsContract.DataUsageFeedback; 33 import android.telephony.PhoneNumberUtils; 34 import android.text.Annotation; 35 import android.text.Spannable; 36 import android.text.SpannableString; 37 import android.text.TextUtils; 38 import android.view.View; 39 import android.widget.ResourceCursorAdapter; 40 import android.widget.TextView; 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 public static final int NORMALIZED_NUMBER = 6; 53 54 private static final String[] PROJECTION_PHONE = { 55 Phone._ID, // 0 56 Phone.CONTACT_ID, // 1 57 Phone.TYPE, // 2 58 Phone.NUMBER, // 3 59 Phone.LABEL, // 4 60 Phone.DISPLAY_NAME, // 5 61 Phone.NORMALIZED_NUMBER, // 6 62 }; 63 64 private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," 65 + Contacts.DISPLAY_NAME + "," + Phone.TYPE; 66 67 private final Context mContext; 68 private final ContentResolver mContentResolver; 69 private final String mDefaultCountryIso; 70 RecipientsAdapter(Context context)71 public RecipientsAdapter(Context context) { 72 // Note that the RecipientsAdapter doesn't support auto-requeries. If we 73 // want to respond to changes in the contacts we're displaying in the drop-down, 74 // code using this adapter would have to add a line such as: 75 // mRecipientsAdapter.setOnDataSetChangedListener(mDataSetChangedListener); 76 // See ComposeMessageActivity for an example. 77 super(context, R.layout.recipient_filter_item, null, false /* no auto-requery */); 78 mContext = context; 79 mContentResolver = context.getContentResolver(); 80 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 81 } 82 83 @Override convertToString(Cursor cursor)84 public final CharSequence convertToString(Cursor cursor) { 85 String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX); 86 if (number == null) { 87 return ""; 88 } 89 number = number.trim(); 90 91 String name = cursor.getString(RecipientsAdapter.NAME_INDEX); 92 int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX); 93 94 String label = cursor.getString(RecipientsAdapter.LABEL_INDEX); 95 CharSequence displayLabel = Phone.getDisplayLabel(mContext, type, label); 96 97 if (name == null) { 98 name = ""; 99 } else { 100 // Names with commas are the bane of the recipient editor's existence. 101 // We've worked around them by using spans, but there are edge cases 102 // where the spans get deleted. Furthermore, having commas in names 103 // can be confusing to the user since commas are used as separators 104 // between recipients. The best solution is to simply remove commas 105 // from names. 106 name = name.replace(", ", " ") 107 .replace(",", " "); // Make sure we leave a space between parts of names. 108 } 109 110 String nameAndNumber = Contact.formatNameAndNumber( name, number, 111 cursor.getString(NORMALIZED_NUMBER)); 112 113 SpannableString out = new SpannableString(nameAndNumber); 114 int len = out.length(); 115 116 if (!TextUtils.isEmpty(name)) { 117 out.setSpan(new Annotation("name", name), 0, len, 118 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 119 } else { 120 out.setSpan(new Annotation("name", number), 0, len, 121 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 122 } 123 124 String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX); 125 out.setSpan(new Annotation("person_id", person_id), 0, len, 126 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 127 out.setSpan(new Annotation("label", displayLabel.toString()), 0, len, 128 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 129 out.setSpan(new Annotation("number", number), 0, len, 130 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 131 132 return out; 133 } 134 135 @Override bindView(View view, Context context, Cursor cursor)136 public final void bindView(View view, Context context, Cursor cursor) { 137 TextView name = (TextView) view.findViewById(R.id.name); 138 name.setText(cursor.getString(NAME_INDEX)); 139 140 TextView label = (TextView) view.findViewById(R.id.label); 141 int type = cursor.getInt(TYPE_INDEX); 142 CharSequence labelText = Phone.getDisplayLabel(mContext, type, 143 cursor.getString(LABEL_INDEX)); 144 // When there's no label, getDisplayLabel() returns a CharSequence of length==1 containing 145 // a unicode non-breaking space. Need to check for that and consider that as "no label". 146 if (labelText.length() == 0 || 147 (labelText.length() == 1 && labelText.charAt(0) == '\u00A0')) { 148 label.setVisibility(View.GONE); 149 } else { 150 label.setText(labelText); 151 label.setVisibility(View.VISIBLE); 152 } 153 154 TextView number = (TextView) view.findViewById(R.id.number); 155 number.setText( 156 PhoneNumberUtils.formatNumber(cursor.getString(NUMBER_INDEX), 157 cursor.getString(NORMALIZED_NUMBER), mDefaultCountryIso)); 158 } 159 160 @Override runQueryOnBackgroundThread(CharSequence constraint)161 public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 162 String phone = ""; 163 String cons = null; 164 165 if (constraint != null) { 166 cons = constraint.toString(); 167 168 if (usefulAsDigits(cons)) { 169 phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons); 170 if (phone.equals(cons)) { 171 phone = ""; 172 } else { 173 phone = phone.trim(); 174 } 175 } 176 } 177 178 Uri uri = Phone.CONTENT_FILTER_URI.buildUpon() 179 .appendPath(cons) 180 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 181 DataUsageFeedback.USAGE_TYPE_SHORT_TEXT) 182 .build(); 183 /* 184 * if we decide to filter based on phone types use a selection 185 * like this. 186 String selection = String.format("%s=%s OR %s=%s OR %s=%s", 187 Phone.TYPE, 188 Phone.TYPE_MOBILE, 189 Phone.TYPE, 190 Phone.TYPE_WORK_MOBILE, 191 Phone.TYPE, 192 Phone.TYPE_MMS); 193 */ 194 Cursor phoneCursor = 195 mContentResolver.query(uri, 196 PROJECTION_PHONE, 197 null, //selection, 198 null, 199 null); 200 201 if (phone.length() > 0) { 202 Object[] result = new Object[7]; 203 result[0] = Integer.valueOf(-1); // ID 204 result[1] = Long.valueOf(-1); // CONTACT_ID 205 result[2] = Integer.valueOf(Phone.TYPE_CUSTOM); // TYPE 206 result[3] = phone; // NUMBER 207 208 /* 209 * The "\u00A0" keeps Phone.getDisplayLabel() from deciding 210 * to display the default label ("Home") next to the transformation 211 * of the letters into numbers. 212 */ 213 result[4] = "\u00A0"; // LABEL 214 result[5] = cons; // NAME 215 result[6] = phone; // NORMALIZED_NUMBER 216 217 MatrixCursor translated = new MatrixCursor(PROJECTION_PHONE, 1); 218 translated.addRow(result); 219 return new MergeCursor(new Cursor[] { translated, phoneCursor }); 220 } else { 221 return phoneCursor; 222 } 223 } 224 225 /** 226 * Returns true if all the characters are meaningful as digits 227 * in a phone number -- letters, digits, and a few punctuation marks. 228 */ usefulAsDigits(CharSequence cons)229 private boolean usefulAsDigits(CharSequence cons) { 230 int len = cons.length(); 231 232 for (int i = 0; i < len; i++) { 233 char c = cons.charAt(i); 234 235 if ((c >= '0') && (c <= '9')) { 236 continue; 237 } 238 if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+') 239 || (c == '#') || (c == '*')) { 240 continue; 241 } 242 if ((c >= 'A') && (c <= 'Z')) { 243 continue; 244 } 245 if ((c >= 'a') && (c <= 'z')) { 246 continue; 247 } 248 249 return false; 250 } 251 252 return true; 253 } 254 } 255