1 /* 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 17 package com.android.loaderapp.util; 18 19 20 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.Cursor; 27 import android.graphics.Bitmap; 28 import android.graphics.BitmapFactory; 29 import android.net.Uri; 30 import android.provider.ContactsContract.Contacts; 31 import android.provider.ContactsContract.Data; 32 import android.provider.ContactsContract.RawContacts; 33 import android.provider.ContactsContract.CommonDataKinds.Email; 34 import android.provider.ContactsContract.CommonDataKinds.Im; 35 import android.provider.ContactsContract.CommonDataKinds.Organization; 36 import android.provider.ContactsContract.CommonDataKinds.Phone; 37 import android.provider.ContactsContract.CommonDataKinds.Photo; 38 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 39 import android.telephony.PhoneNumberUtils; 40 import android.text.TextUtils; 41 42 import java.util.ArrayList; 43 44 public class ContactsUtils { 45 private static final String TAG = "ContactsUtils"; 46 /** 47 * Build the display title for the {@link Data#CONTENT_URI} entry in the 48 * provided cursor, assuming the given mimeType. 49 */ getDisplayLabel(Context context, String mimeType, Cursor cursor)50 public static final CharSequence getDisplayLabel(Context context, 51 String mimeType, Cursor cursor) { 52 // Try finding the type and label for this mimetype 53 int colType; 54 int colLabel; 55 56 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) 57 || Constants.MIME_SMS_ADDRESS.equals(mimeType)) { 58 // Reset to phone mimetype so we generate a label for SMS case 59 mimeType = Phone.CONTENT_ITEM_TYPE; 60 colType = cursor.getColumnIndex(Phone.TYPE); 61 colLabel = cursor.getColumnIndex(Phone.LABEL); 62 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { 63 colType = cursor.getColumnIndex(Email.TYPE); 64 colLabel = cursor.getColumnIndex(Email.LABEL); 65 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) { 66 colType = cursor.getColumnIndex(StructuredPostal.TYPE); 67 colLabel = cursor.getColumnIndex(StructuredPostal.LABEL); 68 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) { 69 colType = cursor.getColumnIndex(Organization.TYPE); 70 colLabel = cursor.getColumnIndex(Organization.LABEL); 71 } else { 72 return null; 73 } 74 75 final int type = cursor.getInt(colType); 76 final CharSequence label = cursor.getString(colLabel); 77 78 return getDisplayLabel(context, mimeType, type, label); 79 } 80 getDisplayLabel(Context context, String mimetype, int type, CharSequence label)81 public static final CharSequence getDisplayLabel(Context context, String mimetype, int type, 82 CharSequence label) { 83 CharSequence display = ""; 84 final int customType; 85 final int defaultType; 86 final int arrayResId; 87 88 if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) { 89 defaultType = Phone.TYPE_HOME; 90 customType = Phone.TYPE_CUSTOM; 91 arrayResId = com.android.internal.R.array.phoneTypes; 92 } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) { 93 defaultType = Email.TYPE_HOME; 94 customType = Email.TYPE_CUSTOM; 95 arrayResId = com.android.internal.R.array.emailAddressTypes; 96 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimetype)) { 97 defaultType = StructuredPostal.TYPE_HOME; 98 customType = StructuredPostal.TYPE_CUSTOM; 99 arrayResId = com.android.internal.R.array.postalAddressTypes; 100 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) { 101 defaultType = Organization.TYPE_WORK; 102 customType = Organization.TYPE_CUSTOM; 103 arrayResId = com.android.internal.R.array.organizationTypes; 104 } else { 105 // Can't return display label for given mimetype. 106 return display; 107 } 108 109 if (type != customType) { 110 CharSequence[] labels = context.getResources().getTextArray(arrayResId); 111 try { 112 display = labels[type - 1]; 113 } catch (ArrayIndexOutOfBoundsException e) { 114 display = labels[defaultType - 1]; 115 } 116 } else { 117 if (!TextUtils.isEmpty(label)) { 118 display = label; 119 } 120 } 121 return display; 122 } 123 124 /** 125 * Opens an InputStream for the person's photo and returns the photo as a Bitmap. 126 * If the person's photo isn't present returns null. 127 * 128 * @param aggCursor the Cursor pointing to the data record containing the photo. 129 * @param bitmapColumnIndex the column index where the photo Uri is stored. 130 * @param options the decoding options, can be set to null 131 * @return the photo Bitmap 132 */ loadContactPhoto(Cursor cursor, int bitmapColumnIndex, BitmapFactory.Options options)133 public static Bitmap loadContactPhoto(Cursor cursor, int bitmapColumnIndex, 134 BitmapFactory.Options options) { 135 if (cursor == null) { 136 return null; 137 } 138 139 byte[] data = cursor.getBlob(bitmapColumnIndex); 140 return BitmapFactory.decodeByteArray(data, 0, data.length, options); 141 } 142 143 /** 144 * Loads a placeholder photo. 145 * 146 * @param placeholderImageResource the resource to use for the placeholder image 147 * @param context the Context 148 * @param options the decoding options, can be set to null 149 * @return the placeholder Bitmap. 150 */ loadPlaceholderPhoto(int placeholderImageResource, Context context, BitmapFactory.Options options)151 public static Bitmap loadPlaceholderPhoto(int placeholderImageResource, Context context, 152 BitmapFactory.Options options) { 153 if (placeholderImageResource == 0) { 154 return null; 155 } 156 return BitmapFactory.decodeResource(context.getResources(), 157 placeholderImageResource, options); 158 } 159 loadContactPhoto(Context context, long photoId, BitmapFactory.Options options)160 public static Bitmap loadContactPhoto(Context context, long photoId, 161 BitmapFactory.Options options) { 162 Cursor photoCursor = null; 163 Bitmap photoBm = null; 164 165 try { 166 photoCursor = context.getContentResolver().query( 167 ContentUris.withAppendedId(Data.CONTENT_URI, photoId), 168 new String[] { Photo.PHOTO }, 169 null, null, null); 170 171 if (photoCursor.moveToFirst() && !photoCursor.isNull(0)) { 172 byte[] photoData = photoCursor.getBlob(0); 173 photoBm = BitmapFactory.decodeByteArray(photoData, 0, 174 photoData.length, options); 175 } 176 } finally { 177 if (photoCursor != null) { 178 photoCursor.close(); 179 } 180 } 181 182 return photoBm; 183 } 184 185 // TODO find a proper place for the canonical version of these 186 public interface ProviderNames { 187 String YAHOO = "Yahoo"; 188 String GTALK = "GTalk"; 189 String MSN = "MSN"; 190 String ICQ = "ICQ"; 191 String AIM = "AIM"; 192 String XMPP = "XMPP"; 193 String JABBER = "JABBER"; 194 String SKYPE = "SKYPE"; 195 String QQ = "QQ"; 196 } 197 198 /** 199 * This looks up the provider name defined in 200 * ProviderNames from the predefined IM protocol id. 201 * This is used for interacting with the IM application. 202 * 203 * @param protocol the protocol ID 204 * @return the provider name the IM app uses for the given protocol, or null if no 205 * provider is defined for the given protocol 206 * @hide 207 */ lookupProviderNameFromId(int protocol)208 public static String lookupProviderNameFromId(int protocol) { 209 switch (protocol) { 210 case Im.PROTOCOL_GOOGLE_TALK: 211 return ProviderNames.GTALK; 212 case Im.PROTOCOL_AIM: 213 return ProviderNames.AIM; 214 case Im.PROTOCOL_MSN: 215 return ProviderNames.MSN; 216 case Im.PROTOCOL_YAHOO: 217 return ProviderNames.YAHOO; 218 case Im.PROTOCOL_ICQ: 219 return ProviderNames.ICQ; 220 case Im.PROTOCOL_JABBER: 221 return ProviderNames.JABBER; 222 case Im.PROTOCOL_SKYPE: 223 return ProviderNames.SKYPE; 224 case Im.PROTOCOL_QQ: 225 return ProviderNames.QQ; 226 } 227 return null; 228 } 229 230 /** 231 * Build {@link Intent} to launch an action for the given {@link Im} or 232 * {@link Email} row. Returns null when missing protocol or data. 233 */ buildImIntent(ContentValues values)234 public static Intent buildImIntent(ContentValues values) { 235 final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE)); 236 237 if (!isEmail && !isProtocolValid(values)) { 238 return null; 239 } 240 241 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : values.getAsInteger(Im.PROTOCOL); 242 243 String host = values.getAsString(Im.CUSTOM_PROTOCOL); 244 String data = values.getAsString(isEmail ? Email.DATA : Im.DATA); 245 if (protocol != Im.PROTOCOL_CUSTOM) { 246 // Try bringing in a well-known host for specific protocols 247 host = ContactsUtils.lookupProviderNameFromId(protocol); 248 } 249 250 if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) { 251 final String authority = host.toLowerCase(); 252 final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority( 253 authority).appendPath(data).build(); 254 return new Intent(Intent.ACTION_SENDTO, imUri); 255 } else { 256 return null; 257 } 258 } 259 isProtocolValid(ContentValues values)260 private static boolean isProtocolValid(ContentValues values) { 261 String protocolString = values.getAsString(Im.PROTOCOL); 262 if (protocolString == null) { 263 return false; 264 } 265 try { 266 Integer.valueOf(protocolString); 267 } catch (NumberFormatException e) { 268 return false; 269 } 270 return true; 271 } 272 queryForContactId(ContentResolver cr, long rawContactId)273 public static long queryForContactId(ContentResolver cr, long rawContactId) { 274 Cursor contactIdCursor = null; 275 long contactId = -1; 276 try { 277 contactIdCursor = cr.query(RawContacts.CONTENT_URI, 278 new String[] {RawContacts.CONTACT_ID}, 279 RawContacts._ID + "=" + rawContactId, null, null); 280 if (contactIdCursor != null && contactIdCursor.moveToFirst()) { 281 contactId = contactIdCursor.getLong(0); 282 } 283 } finally { 284 if (contactIdCursor != null) { 285 contactIdCursor.close(); 286 } 287 } 288 return contactId; 289 } 290 querySuperPrimaryPhone(ContentResolver cr, long contactId)291 public static String querySuperPrimaryPhone(ContentResolver cr, long contactId) { 292 Cursor c = null; 293 String phone = null; 294 try { 295 Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); 296 Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY); 297 298 c = cr.query(dataUri, 299 new String[] {Phone.NUMBER}, 300 Data.MIMETYPE + "=" + Phone.MIMETYPE + 301 " AND " + Data.IS_SUPER_PRIMARY + "=1", 302 null, null); 303 if (c != null && c.moveToFirst()) { 304 // Just return the first one. 305 phone = c.getString(0); 306 } 307 } finally { 308 if (c != null) { 309 c.close(); 310 } 311 } 312 return phone; 313 } 314 queryForRawContactId(ContentResolver cr, long contactId)315 public static long queryForRawContactId(ContentResolver cr, long contactId) { 316 Cursor rawContactIdCursor = null; 317 long rawContactId = -1; 318 try { 319 rawContactIdCursor = cr.query(RawContacts.CONTENT_URI, 320 new String[] {RawContacts._ID}, 321 RawContacts.CONTACT_ID + "=" + contactId, null, null); 322 if (rawContactIdCursor != null && rawContactIdCursor.moveToFirst()) { 323 // Just return the first one. 324 rawContactId = rawContactIdCursor.getLong(0); 325 } 326 } finally { 327 if (rawContactIdCursor != null) { 328 rawContactIdCursor.close(); 329 } 330 } 331 return rawContactId; 332 } 333 queryForAllRawContactIds(ContentResolver cr, long contactId)334 public static ArrayList<Long> queryForAllRawContactIds(ContentResolver cr, long contactId) { 335 Cursor rawContactIdCursor = null; 336 ArrayList<Long> rawContactIds = new ArrayList<Long>(); 337 try { 338 rawContactIdCursor = cr.query(RawContacts.CONTENT_URI, 339 new String[] {RawContacts._ID}, 340 RawContacts.CONTACT_ID + "=" + contactId, null, null); 341 if (rawContactIdCursor != null) { 342 while (rawContactIdCursor.moveToNext()) { 343 rawContactIds.add(rawContactIdCursor.getLong(0)); 344 } 345 } 346 } finally { 347 if (rawContactIdCursor != null) { 348 rawContactIdCursor.close(); 349 } 350 } 351 return rawContactIds; 352 } 353 354 355 /** 356 * Kick off an intent to initiate a call. 357 * 358 * @param phoneNumber must not be null. 359 * @throws NullPointerException when the given argument is null. 360 */ initiateCall(Context context, CharSequence phoneNumber)361 public static void initiateCall(Context context, CharSequence phoneNumber) { 362 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 363 Uri.fromParts("tel", phoneNumber.toString(), null)); 364 context.startActivity(intent); 365 } 366 367 /** 368 * Kick off an intent to initiate an Sms/Mms message. 369 * 370 * @param phoneNumber must not be null. 371 * @throws NullPointerException when the given argument is null. 372 */ initiateSms(Context context, CharSequence phoneNumber)373 public static void initiateSms(Context context, CharSequence phoneNumber) { 374 Intent intent = new Intent(Intent.ACTION_SENDTO, 375 Uri.fromParts("sms", phoneNumber.toString(), null)); 376 context.startActivity(intent); 377 } 378 379 /** 380 * Test if the given {@link CharSequence} contains any graphic characters, 381 * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null. 382 */ isGraphic(CharSequence str)383 public static boolean isGraphic(CharSequence str) { 384 return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str); 385 } 386 387 /** 388 * Returns true if two objects are considered equal. Two null references are equal here. 389 */ areObjectsEqual(Object a, Object b)390 public static boolean areObjectsEqual(Object a, Object b) { 391 return a == b || (a != null && a.equals(b)); 392 } 393 394 /** 395 * Returns true if two data with mimetypes which represent values in contact entries are 396 * considered equal. 397 */ areDataEqual(Context context, CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2)398 public static final boolean areDataEqual(Context context, CharSequence mimetype1, 399 CharSequence data1, CharSequence mimetype2, CharSequence data2) { 400 if (TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1) 401 && TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype2)) { 402 if (data1 == data2) { 403 return true; 404 } 405 if (data1 == null || data2 == null) { 406 return false; 407 } 408 return PhoneNumberUtils.compare(context, data1.toString(), data2.toString()); 409 } else { 410 if (mimetype1 == mimetype2 && data1 == data2) { 411 return true; 412 } 413 return TextUtils.equals(mimetype1, mimetype2) && TextUtils.equals(data1, data2); 414 } 415 } 416 417 /** 418 * Returns true if two {@link Intent}s are both null, or have the same action. 419 */ areIntentActionEqual(Intent a, Intent b)420 public static final boolean areIntentActionEqual(Intent a, Intent b) { 421 if (a == b) { 422 return true; 423 } 424 if (a == null || b == null) { 425 return false; 426 } 427 return TextUtils.equals(a.getAction(), b.getAction()); 428 } 429 } 430