package lb.listviewvariants.utils; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import lb.listviewvariants.BuildConfig; public class ContactImageUtil { @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public static Bitmap loadContactPhoto(Context context,Uri contactUri,int imageSize) { // Instantiates a ContentResolver for retrieving the Uri of the image final ContentResolver contentResolver=context.getContentResolver(); // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the // ContentResolver can return an AssetFileDescriptor for the file. AssetFileDescriptor afd=null; if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // On platforms running Android 4.0 (API version 14) and later, a high resolution image // is available from Photo.DISPLAY_PHOTO. try { // Constructs the content Uri for the image Uri displayImageUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.DISPLAY_PHOTO); // Retrieves an AssetFileDescriptor from the Contacts Provider, using the // constructed Uri afd=contentResolver.openAssetFileDescriptor(displayImageUri,"r"); // If the file exists if(afd!=null) { // Reads and decodes the file to a Bitmap and scales it to the desired size return decodeSampledBitmapFromDescriptor( afd.getFileDescriptor(),imageSize,imageSize); } } catch(FileNotFoundException e) { // Catches file not found exceptions if(BuildConfig.DEBUG) { // Log debug message, this is not an error message as this exception is thrown // when a contact is legitimately missing a contact photo (which will be quite // frequently in a long contacts list). // Log.d(TAG,"Contact photo not found for contact "+contactUri.toString() // +": "+e.toString()); } } finally { // Once the decode is complete, this closes the file. You must do this each time // you access an AssetFileDescriptor; otherwise, every image load you do will open // a new descriptor. if(afd!=null) { try { afd.close(); } catch(IOException e) { // Closing a file descriptor might cause an IOException if the file is // already closed. Nothing extra is needed to handle this. } } } } // If the platform version is less than Android 4.0 (API Level 14), use the only available // image URI, which points to a normal-sized image. try { // Constructs the image Uri from the contact Uri and the directory twig from the // Contacts.Photo table Uri imageUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed // Uri afd=contentResolver.openAssetFileDescriptor(imageUri,"r"); // If the file exists if(afd!=null) { // Reads the image from the file, decodes it, and scales it to the available screen // area return decodeSampledBitmapFromDescriptor( afd.getFileDescriptor(),imageSize,imageSize); } } catch(FileNotFoundException e) { // Catches file not found exceptions if(BuildConfig.DEBUG) { // Log.d(TAG,"Contact photo not found for contact "+contactUri.toString() // +": "+e.toString()); } } finally { // Once the decode is complete, this closes the file. You must do this each time you // access an AssetFileDescriptor; otherwise, every image load you do will open a new // descriptor. if(afd!=null) { try { afd.close(); } catch(IOException e) { // Closing a file descriptor might cause an IOException if the file is // already closed. Ignore this. } } } // If none of the case selectors match, returns null. return null; } /** * Decodes and scales a contact's image from a file pointed to by a Uri in the contact's data, * and returns the result as a Bitmap. The column that contains the Uri varies according to the * platform version. * * @param photoData For platforms prior to Android 3.0, provide the Contact._ID column value. * For Android 3.0 and later, provide the Contact.PHOTO_THUMBNAIL_URI value. * @param imageSize The desired target width and height of the output image in pixels. * @return A Bitmap containing the contact's image, resized to fit the provided image size. If * no thumbnail exists, returns null. */ public static Bitmap loadContactPhotoThumbnail(Context context,String photoData,int imageSize) { // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the // ContentResolver can return an AssetFileDescriptor for the file. AssetFileDescriptor afd=null; // This "try" block catches an Exception if the file descriptor returned from the Contacts // Provider doesn't point to an existing file. try { Uri thumbUri; // If Android 3.0 or later, converts the Uri passed as a string to a Uri object. if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) thumbUri=Uri.parse(photoData); else { // For versions prior to Android 3.0, appends the string argument to the content // Uri for the Contacts table. final Uri contactUri=Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI,photoData); // Appends the content Uri for the Contacts.Photo table to the previously // constructed contact Uri to yield a content URI for the thumbnail image thumbUri=Uri.withAppendedPath(contactUri,ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); } // Retrieves a file descriptor from the Contacts Provider. To learn more about this // feature, read the reference documentation for // ContentResolver#openAssetFileDescriptor. afd=context.getContentResolver().openAssetFileDescriptor(thumbUri,"r"); // Gets a FileDescriptor from the AssetFileDescriptor. A BitmapFactory object can // decode the contents of a file pointed to by a FileDescriptor into a Bitmap. FileDescriptor fileDescriptor=afd.getFileDescriptor(); if(fileDescriptor!=null) { // Decodes a Bitmap from the image pointed to by the FileDescriptor, and scales it // to the specified width and height return decodeSampledBitmapFromDescriptor( fileDescriptor,imageSize,imageSize); } } catch(FileNotFoundException e) { // If the file pointed to by the thumbnail URI doesn't exist, or the file can't be // opened in "read" mode, ContentResolver.openAssetFileDescriptor throws a // FileNotFoundException. // if(BuildConfig.DEBUG) // { // Log.d(TAG,"Contact photo thumbnail not found for contact "+photoData // +": "+e.toString()); // } } finally { // If an AssetFileDescriptor was returned, try to close it if(afd!=null) { try { afd.close(); } catch(IOException e) { // Closing a file descriptor might cause an IOException if the file is // already closed. Nothing extra is needed to handle this. } } } // If the decoding failed, returns null return null; } public static Bitmap decodeSampledBitmapFromDescriptor( FileDescriptor fileDescriptor,int reqWidth,int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options=new BitmapFactory.Options(); options.inJustDecodeBounds=true; BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options); // Calculate inSampleSize options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds=false; return BitmapFactory.decodeFileDescriptor(fileDescriptor,null,options); } /** * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates * the closest inSampleSize that will result in the final decoded bitmap having a width and * height equal to or larger than the requested width and height. This implementation does not * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but * results in a larger bitmap which isn't as useful for caching purposes. * * @param options An options object with out* params already populated (run through a decode* * method with inJustDecodeBounds==true * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return The value to be used for inSampleSize */ public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,int reqHeight) { // Raw height and width of image final int height=options.outHeight; final int width=options.outWidth; int inSampleSize=1; if(height>reqHeight||width>reqWidth) { // Calculate ratios of height and width to requested height and width final int heightRatio=Math.round((float)height/(float)reqHeight); final int widthRatio=Math.round((float)width/(float)reqWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee a final image // with both dimensions larger than or equal to the requested height and width. inSampleSize=heightRatio<widthRatio?heightRatio:widthRatio; // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger // width than height. In these cases the total pixels might still // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger inSampleSize). final float totalPixels=width*height; // Anything more than 2x the requested pixels we'll sample down further final float totalReqPixelsCap=reqWidth*reqHeight*2; while(totalPixels/(inSampleSize*inSampleSize)>totalReqPixelsCap) { inSampleSize++; } } return inSampleSize; } }