DefaultPhotoManager.java revision 35e82d4f9522906f7953667cf5c5f8137ec2f5ac
1/* 2 * Copyright (C) 2014 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 17package com.android.ex.chips; 18 19import android.content.ContentResolver; 20import android.database.Cursor; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.net.Uri; 24import android.os.AsyncTask; 25import android.provider.ContactsContract; 26import android.support.v4.util.LruCache; 27import android.util.Log; 28 29import java.io.ByteArrayOutputStream; 30import java.io.FileNotFoundException; 31import java.io.IOException; 32import java.io.InputStream; 33 34/** 35 * Default implementation of {@link com.android.ex.chips.PhotoManager} that 36 * queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s 37 * photoThumbnailUri. 38 */ 39public class DefaultPhotoManager implements PhotoManager { 40 private static final String TAG = "DefaultPhotoManager"; 41 42 private static final boolean DEBUG = false; 43 44 /** 45 * For reading photos for directory contacts, this is the chunk size for 46 * copying from the {@link InputStream} to the output stream. 47 */ 48 private static final int BUFFER_SIZE = 1024*16; 49 50 private static class PhotoQuery { 51 public static final String[] PROJECTION = { 52 ContactsContract.CommonDataKinds.Photo.PHOTO 53 }; 54 55 public static final int PHOTO = 0; 56 } 57 58 private final ContentResolver mContentResolver; 59 private final LruCache<Uri, byte[]> mPhotoCacheMap; 60 61 public DefaultPhotoManager(ContentResolver contentResolver) { 62 mContentResolver = contentResolver; 63 mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE); 64 } 65 66 @Override 67 public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) { 68 final Uri photoThumbnailUri = entry.getPhotoThumbnailUri(); 69 if (photoThumbnailUri != null) { 70 final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri); 71 if (photoBytes != null) { 72 entry.setPhotoBytes(photoBytes); 73 // notifyDataSetChanged() should be called by a caller. 74 } else { 75 if (DEBUG) { 76 Log.d(TAG, "No photo cache for " + entry.getDisplayName() 77 + ". Fetch one asynchronously"); 78 } 79 fetchPhotoAsync(entry, photoThumbnailUri, callback); 80 } 81 } 82 } 83 84 private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri, 85 final PhotoManagerCallback callback) { 86 final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() { 87 @Override 88 protected byte[] doInBackground(Void... params) { 89 // First try running a query. Images for local contacts are 90 // loaded by sending a query to the ContactsProvider. 91 final Cursor photoCursor = mContentResolver.query( 92 photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null); 93 if (photoCursor != null) { 94 try { 95 if (photoCursor.moveToFirst()) { 96 return photoCursor.getBlob(PhotoQuery.PHOTO); 97 } 98 } finally { 99 photoCursor.close(); 100 } 101 } else { 102 // If the query fails, try streaming the URI directly. 103 // For remote directory images, this URI resolves to the 104 // directory provider and the images are loaded by sending 105 // an openFile call to the provider. 106 try { 107 InputStream is = mContentResolver.openInputStream( 108 photoThumbnailUri); 109 if (is != null) { 110 byte[] buffer = new byte[BUFFER_SIZE]; 111 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 112 try { 113 int size; 114 while ((size = is.read(buffer)) != -1) { 115 baos.write(buffer, 0, size); 116 } 117 } finally { 118 is.close(); 119 } 120 return baos.toByteArray(); 121 } 122 } catch (IOException ex) { 123 // ignore 124 } 125 } 126 return null; 127 } 128 129 @Override 130 protected void onPostExecute(final byte[] photoBytes) { 131 entry.setPhotoBytes(photoBytes); 132 if (photoBytes != null) { 133 mPhotoCacheMap.put(photoThumbnailUri, photoBytes); 134 if (callback != null) { 135 callback.onPhotoBytesAsynchronouslyPopulated(); 136 } 137 } 138 } 139 }; 140 photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); 141 } 142} 143