• 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.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