Home | History | Annotate | Download | only in chips
      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