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