/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ex.chips; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.provider.ContactsContract; import android.support.v4.util.LruCache; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; /** * Default implementation of {@link com.android.ex.chips.PhotoManager} that * queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s * photoThumbnailUri. */ public class DefaultPhotoManager implements PhotoManager { private static final String TAG = "DefaultPhotoManager"; private static final boolean DEBUG = false; /** * For reading photos for directory contacts, this is the chunk size for * copying from the {@link InputStream} to the output stream. */ private static final int BUFFER_SIZE = 1024*16; private static class PhotoQuery { public static final String[] PROJECTION = { ContactsContract.CommonDataKinds.Photo.PHOTO }; public static final int PHOTO = 0; } private final ContentResolver mContentResolver; private final LruCache<Uri, byte[]> mPhotoCacheMap; public DefaultPhotoManager(ContentResolver contentResolver) { mContentResolver = contentResolver; mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE); } @Override public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) { final Uri photoThumbnailUri = entry.getPhotoThumbnailUri(); if (photoThumbnailUri != null) { final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri); if (photoBytes != null) { entry.setPhotoBytes(photoBytes); if (callback != null) { callback.onPhotoBytesPopulated(); } } else { if (DEBUG) { Log.d(TAG, "No photo cache for " + entry.getDisplayName() + ". Fetch one asynchronously"); } fetchPhotoAsync(entry, photoThumbnailUri, callback); } } else if (callback != null) { callback.onPhotoBytesAsyncLoadFailed(); } } private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri, final PhotoManagerCallback callback) { final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() { @Override protected byte[] doInBackground(Void... params) { // First try running a query. Images for local contacts are // loaded by sending a query to the ContactsProvider. final Cursor photoCursor = mContentResolver.query( photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null); if (photoCursor != null) { try { if (photoCursor.moveToFirst()) { return photoCursor.getBlob(PhotoQuery.PHOTO); } } finally { photoCursor.close(); } } else { // If the query fails, try streaming the URI directly. // For remote directory images, this URI resolves to the // directory provider and the images are loaded by sending // an openFile call to the provider. try { InputStream is = mContentResolver.openInputStream( photoThumbnailUri); if (is != null) { byte[] buffer = new byte[BUFFER_SIZE]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { int size; while ((size = is.read(buffer)) != -1) { baos.write(buffer, 0, size); } } finally { is.close(); } return baos.toByteArray(); } } catch (IOException ex) { // ignore } } return null; } @Override protected void onPostExecute(final byte[] photoBytes) { entry.setPhotoBytes(photoBytes); if (photoBytes != null) { mPhotoCacheMap.put(photoThumbnailUri, photoBytes); if (callback != null) { callback.onPhotoBytesAsynchronouslyPopulated(); } } else if (callback != null) { callback.onPhotoBytesAsyncLoadFailed(); } } }; photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); } }