18af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein/*
28af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * Copyright (C) 2014 The Android Open Source Project
38af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein *
48af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License");
58af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * you may not use this file except in compliance with the License.
68af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * You may obtain a copy of the License at
78af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein *
88af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein *      http://www.apache.org/licenses/LICENSE-2.0
98af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein *
108af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software
118af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS,
128af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * See the License for the specific language governing permissions and
148af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * limitations under the License.
158af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein */
168af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
178af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinpackage com.android.ex.chips;
188af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
198af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.content.ContentResolver;
208af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.database.Cursor;
218af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.net.Uri;
228af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.os.AsyncTask;
238af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.provider.ContactsContract;
248af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.support.v4.util.LruCache;
258af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport android.util.Log;
268af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
278af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport java.io.ByteArrayOutputStream;
288af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport java.io.IOException;
298af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinimport java.io.InputStream;
308af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
318af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein/**
328af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * Default implementation of {@link com.android.ex.chips.PhotoManager} that
338af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s
348af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein * photoThumbnailUri.
358af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein */
368af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sappersteinpublic class DefaultPhotoManager implements PhotoManager {
378af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private static final String TAG = "DefaultPhotoManager";
388af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
398af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private static final boolean DEBUG = false;
408af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
418af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    /**
428af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein     * For reading photos for directory contacts, this is the chunk size for
438af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein     * copying from the {@link InputStream} to the output stream.
448af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein     */
458af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private static final int BUFFER_SIZE = 1024*16;
468af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
478af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private static class PhotoQuery {
488af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        public static final String[] PROJECTION = {
498af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            ContactsContract.CommonDataKinds.Photo.PHOTO
508af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        };
518af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
528af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        public static final int PHOTO = 0;
538af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    }
548af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
558af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private final ContentResolver mContentResolver;
568af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private final LruCache<Uri, byte[]> mPhotoCacheMap;
578af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
588af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    public DefaultPhotoManager(ContentResolver contentResolver) {
598af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        mContentResolver = contentResolver;
608af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE);
618af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    }
628af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
638af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    @Override
648af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) {
658af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
668af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        if (photoThumbnailUri != null) {
678af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri);
688af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            if (photoBytes != null) {
698af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                entry.setPhotoBytes(photoBytes);
700efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao                if (callback != null) {
7150429c51adbd79c5d7dd5beb2c267daf9465f20fAndrew Sapperstein                    callback.onPhotoBytesPopulated();
720efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao                }
738af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            } else {
748af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                if (DEBUG) {
758af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    Log.d(TAG, "No photo cache for " + entry.getDisplayName()
768af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            + ". Fetch one asynchronously");
778af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                }
788af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                fetchPhotoAsync(entry, photoThumbnailUri, callback);
798af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            }
800efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao        } else if (callback != null) {
810efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao            callback.onPhotoBytesAsyncLoadFailed();
828af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        }
838af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    }
848af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
858af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri,
868af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            final PhotoManagerCallback callback) {
878af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() {
888af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            @Override
898af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            protected byte[] doInBackground(Void... params) {
908af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                // First try running a query. Images for local contacts are
918af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                // loaded by sending a query to the ContactsProvider.
928af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                final Cursor photoCursor = mContentResolver.query(
938af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null);
948af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                if (photoCursor != null) {
958af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    try {
968af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        if (photoCursor.moveToFirst()) {
978af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            return photoCursor.getBlob(PhotoQuery.PHOTO);
988af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        }
998af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    } finally {
1008af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        photoCursor.close();
1018af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    }
1028af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                } else {
1038af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    // If the query fails, try streaming the URI directly.
1048af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    // For remote directory images, this URI resolves to the
1058af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    // directory provider and the images are loaded by sending
1068af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    // an openFile call to the provider.
1078af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    try {
1088af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        InputStream is = mContentResolver.openInputStream(
1098af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                photoThumbnailUri);
1108af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        if (is != null) {
1118af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            byte[] buffer = new byte[BUFFER_SIZE];
1128af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
1138af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            try {
1148af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                int size;
1158af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                while ((size = is.read(buffer)) != -1) {
1168af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                    baos.write(buffer, 0, size);
1178af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                }
1188af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            } finally {
1198af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                                is.close();
1208af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            }
1218af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                            return baos.toByteArray();
1228af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        }
1238af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    } catch (IOException ex) {
1248af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        // ignore
1258af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    }
1268af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                }
1278af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                return null;
1288af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            }
1298af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein
1308af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            @Override
1318af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            protected void onPostExecute(final byte[] photoBytes) {
1328af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                entry.setPhotoBytes(photoBytes);
1338af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                if (photoBytes != null) {
1348af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
1358af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    if (callback != null) {
1368af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                        callback.onPhotoBytesAsynchronouslyPopulated();
1378af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    }
1380efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao                } else if (callback != null) {
1390efdc53cd0d040c1a27a7d39003916e54e284be2Jin Cao                    callback.onPhotoBytesAsyncLoadFailed();
1408af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                }
1418af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            }
1428af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        };
1438af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
1448af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    }
1458af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein}
146