blob: 68e7c6ae4ed313ff72a0be78f3ffe4d4f1158362 [file] [log] [blame]
Evan Millar45e0ed32009-06-01 16:44:38 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Evan Millar66388be2009-05-28 15:41:07 -070017package com.android.contacts;
18
19
Jeff Hamilton1bf258e2009-12-15 16:55:49 -060020import com.android.contacts.model.ContactsSource;
21import com.android.contacts.util.Constants;
22
Evan Millar45e0ed32009-06-01 16:44:38 -070023import android.content.ContentResolver;
Evan Millar2c1cc832009-07-13 11:08:06 -070024import android.content.ContentUris;
Jeff Sharkey624ddc32009-10-01 21:32:19 -070025import android.content.ContentValues;
Evan Millar45e0ed32009-06-01 16:44:38 -070026import android.content.Context;
Jeff Sharkey3f0b7b82009-08-12 11:28:53 -070027import android.content.Intent;
Evan Millar45e0ed32009-06-01 16:44:38 -070028import android.database.Cursor;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
Evan Millarf19104c2009-09-02 17:53:25 -070031import android.graphics.drawable.Drawable;
Bai Taoba344222010-07-28 17:50:23 -070032import android.location.Country;
33import android.location.CountryDetector;
Neel Parekh2ad90a32009-09-20 19:08:50 -070034import android.net.Uri;
35import android.provider.ContactsContract.Contacts;
36import android.provider.ContactsContract.Data;
37import android.provider.ContactsContract.RawContacts;
Evan Millar66388be2009-05-28 15:41:07 -070038import android.provider.ContactsContract.CommonDataKinds.Email;
39import android.provider.ContactsContract.CommonDataKinds.Im;
40import android.provider.ContactsContract.CommonDataKinds.Organization;
41import android.provider.ContactsContract.CommonDataKinds.Phone;
Evan Millar2c1cc832009-07-13 11:08:06 -070042import android.provider.ContactsContract.CommonDataKinds.Photo;
Jeff Sharkeyc6ad3ab2009-07-21 19:30:15 -070043import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
Makoto Onukic710b0e2009-10-13 15:43:24 -070044import android.telephony.PhoneNumberUtils;
Evan Millar66388be2009-05-28 15:41:07 -070045import android.text.TextUtils;
Evan Millar11d628c2009-09-02 08:55:01 -070046import android.view.LayoutInflater;
47import android.view.View;
48import android.view.ViewGroup;
49import android.widget.ImageView;
50import android.widget.TextView;
Evan Millar66388be2009-05-28 15:41:07 -070051
Neel Parekh2ad90a32009-09-20 19:08:50 -070052import java.util.ArrayList;
53
Evan Millar66388be2009-05-28 15:41:07 -070054public class ContactsUtils {
Evan Millar11d628c2009-09-02 08:55:01 -070055 private static final String TAG = "ContactsUtils";
Daniel Lehmannd8b0a052010-03-25 17:41:00 -070056 private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
Jeff Sharkey39261272009-06-03 19:15:09 -070057 /**
58 * Build the display title for the {@link Data#CONTENT_URI} entry in the
59 * provided cursor, assuming the given mimeType.
60 */
61 public static final CharSequence getDisplayLabel(Context context,
62 String mimeType, Cursor cursor) {
63 // Try finding the type and label for this mimetype
64 int colType;
65 int colLabel;
66
Jeff Sharkey39261272009-06-03 19:15:09 -070067 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Jeff Sharkey49d17b32009-09-07 02:14:21 -070068 || Constants.MIME_SMS_ADDRESS.equals(mimeType)) {
Jeff Sharkey39261272009-06-03 19:15:09 -070069 // Reset to phone mimetype so we generate a label for SMS case
70 mimeType = Phone.CONTENT_ITEM_TYPE;
71 colType = cursor.getColumnIndex(Phone.TYPE);
72 colLabel = cursor.getColumnIndex(Phone.LABEL);
73 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
74 colType = cursor.getColumnIndex(Email.TYPE);
75 colLabel = cursor.getColumnIndex(Email.LABEL);
Jeff Sharkeyc6ad3ab2009-07-21 19:30:15 -070076 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
77 colType = cursor.getColumnIndex(StructuredPostal.TYPE);
78 colLabel = cursor.getColumnIndex(StructuredPostal.LABEL);
Jeff Sharkey39261272009-06-03 19:15:09 -070079 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
80 colType = cursor.getColumnIndex(Organization.TYPE);
81 colLabel = cursor.getColumnIndex(Organization.LABEL);
82 } else {
83 return null;
84 }
85
86 final int type = cursor.getInt(colType);
87 final CharSequence label = cursor.getString(colLabel);
88
89 return getDisplayLabel(context, mimeType, type, label);
90 }
91
Evan Millar66388be2009-05-28 15:41:07 -070092 public static final CharSequence getDisplayLabel(Context context, String mimetype, int type,
93 CharSequence label) {
94 CharSequence display = "";
95 final int customType;
96 final int defaultType;
97 final int arrayResId;
98
99 if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
100 defaultType = Phone.TYPE_HOME;
101 customType = Phone.TYPE_CUSTOM;
102 arrayResId = com.android.internal.R.array.phoneTypes;
103 } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
104 defaultType = Email.TYPE_HOME;
105 customType = Email.TYPE_CUSTOM;
106 arrayResId = com.android.internal.R.array.emailAddressTypes;
Jeff Sharkeyc6ad3ab2009-07-21 19:30:15 -0700107 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimetype)) {
108 defaultType = StructuredPostal.TYPE_HOME;
109 customType = StructuredPostal.TYPE_CUSTOM;
Evan Millar66388be2009-05-28 15:41:07 -0700110 arrayResId = com.android.internal.R.array.postalAddressTypes;
111 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) {
Dmitri Plotnikov48cf72b2009-07-17 11:00:26 -0700112 defaultType = Organization.TYPE_WORK;
Evan Millar66388be2009-05-28 15:41:07 -0700113 customType = Organization.TYPE_CUSTOM;
114 arrayResId = com.android.internal.R.array.organizationTypes;
115 } else {
116 // Can't return display label for given mimetype.
117 return display;
118 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700119
Evan Millar66388be2009-05-28 15:41:07 -0700120 if (type != customType) {
121 CharSequence[] labels = context.getResources().getTextArray(arrayResId);
122 try {
123 display = labels[type - 1];
124 } catch (ArrayIndexOutOfBoundsException e) {
125 display = labels[defaultType - 1];
126 }
127 } else {
128 if (!TextUtils.isEmpty(label)) {
129 display = label;
130 }
131 }
132 return display;
133 }
Evan Millar45e0ed32009-06-01 16:44:38 -0700134
Evan Millar45e0ed32009-06-01 16:44:38 -0700135 /**
136 * Opens an InputStream for the person's photo and returns the photo as a Bitmap.
137 * If the person's photo isn't present returns null.
138 *
139 * @param aggCursor the Cursor pointing to the data record containing the photo.
140 * @param bitmapColumnIndex the column index where the photo Uri is stored.
141 * @param options the decoding options, can be set to null
142 * @return the photo Bitmap
143 */
Evan Millar0a40ffa2009-06-18 16:49:08 -0700144 public static Bitmap loadContactPhoto(Cursor cursor, int bitmapColumnIndex,
Evan Millar45e0ed32009-06-01 16:44:38 -0700145 BitmapFactory.Options options) {
Evan Millar0a40ffa2009-06-18 16:49:08 -0700146 if (cursor == null) {
Evan Millar45e0ed32009-06-01 16:44:38 -0700147 return null;
148 }
149
Evan Millar7911ff52009-07-21 15:55:18 -0700150 byte[] data = cursor.getBlob(bitmapColumnIndex);
Evan Millar45e0ed32009-06-01 16:44:38 -0700151 return BitmapFactory.decodeByteArray(data, 0, data.length, options);
152 }
153
154 /**
155 * Loads a placeholder photo.
156 *
157 * @param placeholderImageResource the resource to use for the placeholder image
158 * @param context the Context
159 * @param options the decoding options, can be set to null
160 * @return the placeholder Bitmap.
161 */
162 public static Bitmap loadPlaceholderPhoto(int placeholderImageResource, Context context,
163 BitmapFactory.Options options) {
164 if (placeholderImageResource == 0) {
165 return null;
166 }
167 return BitmapFactory.decodeResource(context.getResources(),
168 placeholderImageResource, options);
169 }
170
Evan Millar7911ff52009-07-21 15:55:18 -0700171 public static Bitmap loadContactPhoto(Context context, long photoId,
Evan Millar2c1cc832009-07-13 11:08:06 -0700172 BitmapFactory.Options options) {
173 Cursor photoCursor = null;
174 Bitmap photoBm = null;
175
176 try {
177 photoCursor = context.getContentResolver().query(
178 ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
179 new String[] { Photo.PHOTO },
180 null, null, null);
181
182 if (photoCursor.moveToFirst() && !photoCursor.isNull(0)) {
183 byte[] photoData = photoCursor.getBlob(0);
184 photoBm = BitmapFactory.decodeByteArray(photoData, 0,
185 photoData.length, options);
186 }
187 } finally {
188 if (photoCursor != null) {
189 photoCursor.close();
190 }
191 }
192
193 return photoBm;
194 }
195
Jeff Hamilton1bf258e2009-12-15 16:55:49 -0600196 // TODO find a proper place for the canonical version of these
197 public interface ProviderNames {
198 String YAHOO = "Yahoo";
199 String GTALK = "GTalk";
200 String MSN = "MSN";
201 String ICQ = "ICQ";
202 String AIM = "AIM";
203 String XMPP = "XMPP";
204 String JABBER = "JABBER";
205 String SKYPE = "SKYPE";
206 String QQ = "QQ";
207 }
208
Evan Millar66388be2009-05-28 15:41:07 -0700209 /**
210 * This looks up the provider name defined in
Jeff Hamiltoneffb7ff2009-12-17 16:29:40 -0600211 * ProviderNames from the predefined IM protocol id.
Evan Millar66388be2009-05-28 15:41:07 -0700212 * This is used for interacting with the IM application.
213 *
214 * @param protocol the protocol ID
215 * @return the provider name the IM app uses for the given protocol, or null if no
216 * provider is defined for the given protocol
217 * @hide
218 */
219 public static String lookupProviderNameFromId(int protocol) {
220 switch (protocol) {
221 case Im.PROTOCOL_GOOGLE_TALK:
222 return ProviderNames.GTALK;
223 case Im.PROTOCOL_AIM:
224 return ProviderNames.AIM;
225 case Im.PROTOCOL_MSN:
226 return ProviderNames.MSN;
227 case Im.PROTOCOL_YAHOO:
228 return ProviderNames.YAHOO;
229 case Im.PROTOCOL_ICQ:
230 return ProviderNames.ICQ;
231 case Im.PROTOCOL_JABBER:
232 return ProviderNames.JABBER;
233 case Im.PROTOCOL_SKYPE:
234 return ProviderNames.SKYPE;
235 case Im.PROTOCOL_QQ:
236 return ProviderNames.QQ;
237 }
238 return null;
239 }
240
Jeff Sharkey624ddc32009-10-01 21:32:19 -0700241 /**
242 * Build {@link Intent} to launch an action for the given {@link Im} or
243 * {@link Email} row. Returns null when missing protocol or data.
244 */
245 public static Intent buildImIntent(ContentValues values) {
246 final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
Evan Millar43455182009-10-08 10:24:12 -0700247
248 if (!isEmail && !isProtocolValid(values)) {
249 return null;
250 }
251
Jeff Sharkey624ddc32009-10-01 21:32:19 -0700252 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : values.getAsInteger(Im.PROTOCOL);
253
254 String host = values.getAsString(Im.CUSTOM_PROTOCOL);
255 String data = values.getAsString(isEmail ? Email.DATA : Im.DATA);
256 if (protocol != Im.PROTOCOL_CUSTOM) {
257 // Try bringing in a well-known host for specific protocols
258 host = ContactsUtils.lookupProviderNameFromId(protocol);
259 }
260
261 if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) {
262 final String authority = host.toLowerCase();
263 final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
264 authority).appendPath(data).build();
265 return new Intent(Intent.ACTION_SENDTO, imUri);
266 } else {
267 return null;
268 }
269 }
270
Evan Millar43455182009-10-08 10:24:12 -0700271 private static boolean isProtocolValid(ContentValues values) {
272 String protocolString = values.getAsString(Im.PROTOCOL);
273 if (protocolString == null) {
274 return false;
275 }
276 try {
277 Integer.valueOf(protocolString);
278 } catch (NumberFormatException e) {
279 return false;
280 }
281 return true;
282 }
283
Evan Millar8a79cee2009-08-19 17:20:49 -0700284 public static long queryForContactId(ContentResolver cr, long rawContactId) {
285 Cursor contactIdCursor = null;
286 long contactId = -1;
287 try {
288 contactIdCursor = cr.query(RawContacts.CONTENT_URI,
289 new String[] {RawContacts.CONTACT_ID},
290 RawContacts._ID + "=" + rawContactId, null, null);
291 if (contactIdCursor != null && contactIdCursor.moveToFirst()) {
292 contactId = contactIdCursor.getLong(0);
293 }
294 } finally {
295 if (contactIdCursor != null) {
296 contactIdCursor.close();
297 }
298 }
299 return contactId;
300 }
Evan Millar11d628c2009-09-02 08:55:01 -0700301
Evan Millar2cd51002009-09-02 14:33:38 -0700302 public static String querySuperPrimaryPhone(ContentResolver cr, long contactId) {
303 Cursor c = null;
304 String phone = null;
305 try {
306 Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
Jeff Sharkey49d17b32009-09-07 02:14:21 -0700307 Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
Evan Millar2cd51002009-09-02 14:33:38 -0700308
309 c = cr.query(dataUri,
310 new String[] {Phone.NUMBER},
311 Data.MIMETYPE + "=" + Phone.MIMETYPE +
312 " AND " + Data.IS_SUPER_PRIMARY + "=1",
313 null, null);
314 if (c != null && c.moveToFirst()) {
315 // Just return the first one.
316 phone = c.getString(0);
317 }
318 } finally {
319 if (c != null) {
320 c.close();
321 }
322 }
323 return phone;
324 }
325
326 public static long queryForRawContactId(ContentResolver cr, long contactId) {
327 Cursor rawContactIdCursor = null;
328 long rawContactId = -1;
329 try {
330 rawContactIdCursor = cr.query(RawContacts.CONTENT_URI,
331 new String[] {RawContacts._ID},
332 RawContacts.CONTACT_ID + "=" + contactId, null, null);
333 if (rawContactIdCursor != null && rawContactIdCursor.moveToFirst()) {
334 // Just return the first one.
335 rawContactId = rawContactIdCursor.getLong(0);
336 }
337 } finally {
338 if (rawContactIdCursor != null) {
339 rawContactIdCursor.close();
340 }
341 }
342 return rawContactId;
343 }
344
Neel Parekh2ad90a32009-09-20 19:08:50 -0700345 public static ArrayList<Long> queryForAllRawContactIds(ContentResolver cr, long contactId) {
346 Cursor rawContactIdCursor = null;
347 ArrayList<Long> rawContactIds = new ArrayList<Long>();
348 try {
349 rawContactIdCursor = cr.query(RawContacts.CONTENT_URI,
350 new String[] {RawContacts._ID},
351 RawContacts.CONTACT_ID + "=" + contactId, null, null);
352 if (rawContactIdCursor != null) {
353 while (rawContactIdCursor.moveToNext()) {
354 rawContactIds.add(rawContactIdCursor.getLong(0));
355 }
356 }
357 } finally {
358 if (rawContactIdCursor != null) {
359 rawContactIdCursor.close();
360 }
361 }
362 return rawContactIds;
363 }
364
Evan Millar11d628c2009-09-02 08:55:01 -0700365
366 /**
367 * Utility for creating a standard tab indicator view.
368 *
369 * @param parent The parent ViewGroup to attach the new view to.
370 * @param label The label to display in the tab indicator. If null, not label will be displayed.
371 * @param icon The icon to display. If null, no icon will be displayed.
372 * @return The tab indicator View.
373 */
374 public static View createTabIndicatorView(ViewGroup parent, CharSequence label, Drawable icon) {
375 final LayoutInflater inflater = (LayoutInflater)parent.getContext().getSystemService(
376 Context.LAYOUT_INFLATER_SERVICE);
377 final View tabIndicator = inflater.inflate(R.layout.tab_indicator, parent, false);
378 tabIndicator.getBackground().setDither(true);
379
380 final TextView tv = (TextView) tabIndicator.findViewById(R.id.tab_title);
381 tv.setText(label);
382
383 final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.tab_icon);
384 iconView.setImageDrawable(icon);
385
386 return tabIndicator;
387 }
388
389 /**
390 * Utility for creating a standard tab indicator view.
391 *
Evan Millar11d628c2009-09-02 08:55:01 -0700392 * @param parent The parent ViewGroup to attach the new view to.
393 * @param source The {@link ContactsSource} to build the tab view from.
394 * @return The tab indicator View.
395 */
396 public static View createTabIndicatorView(ViewGroup parent, ContactsSource source) {
397 Drawable icon = null;
398 if (source != null) {
Jeff Sharkeyab066932009-09-21 09:55:30 -0700399 icon = source.getDisplayIcon(parent.getContext());
Evan Millar11d628c2009-09-02 08:55:01 -0700400 }
401 return createTabIndicatorView(parent, null, icon);
402 }
Evan Millar14fecb62009-09-09 09:23:12 -0700403
404 /**
405 * Kick off an intent to initiate a call.
Daisuke Miyakawa46befc32009-10-28 10:13:57 +0900406 *
407 * @param phoneNumber must not be null.
408 * @throws NullPointerException when the given argument is null.
Evan Millar14fecb62009-09-09 09:23:12 -0700409 */
410 public static void initiateCall(Context context, CharSequence phoneNumber) {
411 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
412 Uri.fromParts("tel", phoneNumber.toString(), null));
413 context.startActivity(intent);
414 }
415
416 /**
417 * Kick off an intent to initiate an Sms/Mms message.
Daisuke Miyakawa46befc32009-10-28 10:13:57 +0900418 *
419 * @param phoneNumber must not be null.
420 * @throws NullPointerException when the given argument is null.
Evan Millar14fecb62009-09-09 09:23:12 -0700421 */
422 public static void initiateSms(Context context, CharSequence phoneNumber) {
423 Intent intent = new Intent(Intent.ACTION_SENDTO,
424 Uri.fromParts("sms", phoneNumber.toString(), null));
425 context.startActivity(intent);
426 }
Jeff Sharkeye31dac82009-10-08 11:13:47 -0700427
428 /**
429 * Test if the given {@link CharSequence} contains any graphic characters,
430 * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
431 */
432 public static boolean isGraphic(CharSequence str) {
433 return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
434 }
Makoto Onukic710b0e2009-10-13 15:43:24 -0700435
436 /**
437 * Returns true if two objects are considered equal. Two null references are equal here.
438 */
439 public static boolean areObjectsEqual(Object a, Object b) {
440 return a == b || (a != null && a.equals(b));
441 }
442
443 /**
444 * Returns true if two data with mimetypes which represent values in contact entries are
Daniel Lehmannd8b0a052010-03-25 17:41:00 -0700445 * considered equal for collapsing in the GUI. For caller-id, use
446 * {@link PhoneNumberUtils#compare(Context, String, String)} instead
Makoto Onukic710b0e2009-10-13 15:43:24 -0700447 */
Daniel Lehmannd8b0a052010-03-25 17:41:00 -0700448 public static final boolean shouldCollapse(Context context, CharSequence mimetype1,
Makoto Onukic710b0e2009-10-13 15:43:24 -0700449 CharSequence data1, CharSequence mimetype2, CharSequence data2) {
450 if (TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1)
451 && TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype2)) {
452 if (data1 == data2) {
453 return true;
454 }
455 if (data1 == null || data2 == null) {
456 return false;
457 }
Daniel Lehmannd8b0a052010-03-25 17:41:00 -0700458
459 // If the number contains semicolons, PhoneNumberUtils.compare
460 // only checks the substring before that (which is fine for caller-id usually)
461 // but not for collapsing numbers. so we check each segment indidually to be more strict
462 // TODO: This should be replaced once we have a more robust phonenumber-library
463 String[] dataParts1 = data1.toString().split(WAIT_SYMBOL_AS_STRING);
464 String[] dataParts2 = data2.toString().split(WAIT_SYMBOL_AS_STRING);
465 if (dataParts1.length != dataParts2.length) {
466 return false;
467 }
468 for (int i = 0; i < dataParts1.length; i++) {
469 if (!PhoneNumberUtils.compare(context, dataParts1[i], dataParts2[i])) {
470 return false;
471 }
472 }
473
474 return true;
Makoto Onukic710b0e2009-10-13 15:43:24 -0700475 } else {
476 if (mimetype1 == mimetype2 && data1 == data2) {
477 return true;
478 }
479 return TextUtils.equals(mimetype1, mimetype2) && TextUtils.equals(data1, data2);
480 }
481 }
482
483 /**
484 * Returns true if two {@link Intent}s are both null, or have the same action.
485 */
486 public static final boolean areIntentActionEqual(Intent a, Intent b) {
487 if (a == b) {
488 return true;
489 }
490 if (a == null || b == null) {
491 return false;
492 }
493 return TextUtils.equals(a.getAction(), b.getAction());
494 }
Bai Taoba344222010-07-28 17:50:23 -0700495
496 /**
497 * @return The ISO 3166-1 two letters country code of the country the user
498 * is in.
499 */
500 public static final String getCurrentCountryIso(Context context) {
501 CountryDetector detector =
502 (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR);
503 Country country = detector.detectCountry();
504 return country.getCountryIso();
505 }
Evan Millar66388be2009-05-28 15:41:07 -0700506}