• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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