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