blob: 344fe1e514975c6facf9b5a0ea519446f62fb726 [file] [log] [blame]
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -07001/*
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;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070021import android.net.Uri;
22import android.os.AsyncTask;
23import android.provider.ContactsContract;
xusai176911f2015-05-28 16:24:50 +080024import android.provider.ContactsContract.Data;
25import android.provider.ContactsContract.RawContacts;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070026import android.support.v4.util.LruCache;
27import android.util.Log;
28
29import java.io.ByteArrayOutputStream;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070030import java.io.IOException;
31import java.io.InputStream;
32
33/**
34 * Default implementation of {@link com.android.ex.chips.PhotoManager} that
35 * queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s
36 * photoThumbnailUri.
37 */
38public class DefaultPhotoManager implements PhotoManager {
39 private static final String TAG = "DefaultPhotoManager";
40
41 private static final boolean DEBUG = false;
42
43 /**
44 * For reading photos for directory contacts, this is the chunk size for
45 * copying from the {@link InputStream} to the output stream.
46 */
47 private static final int BUFFER_SIZE = 1024*16;
48
49 private static class PhotoQuery {
50 public static final String[] PROJECTION = {
51 ContactsContract.CommonDataKinds.Photo.PHOTO
52 };
53
54 public static final int PHOTO = 0;
55 }
56
57 private final ContentResolver mContentResolver;
58 private final LruCache<Uri, byte[]> mPhotoCacheMap;
59
60 public DefaultPhotoManager(ContentResolver contentResolver) {
61 mContentResolver = contentResolver;
62 mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE);
63 }
64
65 @Override
66 public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) {
67 final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
68 if (photoThumbnailUri != null) {
xusai176911f2015-05-28 16:24:50 +080069 getPhotoAsync(entry, callback, photoThumbnailUri);
70 } else {
71 long contactId = findRawContactIdOfContact(entry.getDestination(), entry.getDisplayName());
72 if ( contactId != -1 && entry.getContactId() != RecipientEntry.INVALID_CONTACT) {
73 Uri contructedUri = Uri.parse("content://com.android.contacts/contacts/"
74 +String.valueOf(contactId)+"/photo");
75 getPhotoAsync(entry, callback, contructedUri);
76 } else if (callback != null) {
77 callback.onPhotoBytesAsyncLoadFailed();
78 }
79 }
80 }
81
82 private void getPhotoAsync(RecipientEntry entry, PhotoManagerCallback callback, Uri uri) {
83 final byte[] photoBytes = mPhotoCacheMap.get(uri);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070084 if (photoBytes != null) {
85 entry.setPhotoBytes(photoBytes);
Jin Cao0efdc532014-06-04 15:35:16 -070086 if (callback != null) {
Andrew Sapperstein50429c52014-06-12 15:12:10 -070087 callback.onPhotoBytesPopulated();
Jin Cao0efdc532014-06-04 15:35:16 -070088 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070089 } else {
90 if (DEBUG) {
91 Log.d(TAG, "No photo cache for " + entry.getDisplayName()
92 + ". Fetch one asynchronously");
93 }
xusai176911f2015-05-28 16:24:50 +080094 fetchPhotoAsync(entry, uri, callback);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070095 }
xusai176911f2015-05-28 16:24:50 +080096 }
97
98 private long findRawContactIdOfContact(String destination, String displayname) {
99 final Cursor cursor = mContentResolver.query(Data.CONTENT_URI,
100 new String[] { RawContacts.CONTACT_ID, Data.DATA1 },
101 Data.DATA1 + "=? OR "+Data.DATA2+"=?",
102 new String[] { destination, displayname },
103 RawContacts._ID + " asc");
104
105 long contactId1 = -1;
106 long contactId2 = -1;
107 try {
108 while (cursor.moveToNext()) {
109 if (cursor.getString(1).equals(displayname)) {
110 contactId1 = cursor.getLong(0);
111 if (contactId2 != -1) break;
112 }
113 if (cursor.getString(1).equals(destination)) {
114 contactId2 = cursor.getLong(0);
115 if (contactId1 != -1) break;
116 }
117 }
118
119 if (contactId1 == contactId2 && contactId1 != -1) {
120 return contactId1;
121 }
122 } finally {
123 cursor.close();
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700124 }
xusai176911f2015-05-28 16:24:50 +0800125
126 return -1;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700127 }
128
129 private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri,
130 final PhotoManagerCallback callback) {
131 final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() {
132 @Override
133 protected byte[] doInBackground(Void... params) {
134 // First try running a query. Images for local contacts are
135 // loaded by sending a query to the ContactsProvider.
136 final Cursor photoCursor = mContentResolver.query(
137 photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null);
138 if (photoCursor != null) {
139 try {
140 if (photoCursor.moveToFirst()) {
141 return photoCursor.getBlob(PhotoQuery.PHOTO);
142 }
143 } finally {
144 photoCursor.close();
145 }
146 } else {
147 // If the query fails, try streaming the URI directly.
148 // For remote directory images, this URI resolves to the
149 // directory provider and the images are loaded by sending
150 // an openFile call to the provider.
151 try {
152 InputStream is = mContentResolver.openInputStream(
153 photoThumbnailUri);
154 if (is != null) {
155 byte[] buffer = new byte[BUFFER_SIZE];
156 ByteArrayOutputStream baos = new ByteArrayOutputStream();
157 try {
158 int size;
159 while ((size = is.read(buffer)) != -1) {
160 baos.write(buffer, 0, size);
161 }
162 } finally {
163 is.close();
164 }
165 return baos.toByteArray();
166 }
167 } catch (IOException ex) {
168 // ignore
169 }
170 }
171 return null;
172 }
173
174 @Override
175 protected void onPostExecute(final byte[] photoBytes) {
176 entry.setPhotoBytes(photoBytes);
177 if (photoBytes != null) {
178 mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
179 if (callback != null) {
180 callback.onPhotoBytesAsynchronouslyPopulated();
181 }
Jin Cao0efdc532014-06-04 15:35:16 -0700182 } else if (callback != null) {
183 callback.onPhotoBytesAsyncLoadFailed();
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700184 }
185 }
186 };
187 photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
188 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700189}