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