12ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein/* 22ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * Copyright (C) 2012 Google Inc. 32ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * Licensed to The Android Open Source Project. 42ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * 52ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License"); 62ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * you may not use this file except in compliance with the License. 72ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * You may obtain a copy of the License at 82ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * 92ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * http://www.apache.org/licenses/LICENSE-2.0 102ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * 112ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software 122ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS, 132ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 142ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * See the License for the specific language governing permissions and 152ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * limitations under the License. 162ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein */ 172ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 182ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinpackage com.android.mail.ui; 192ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 20986d7a633359c1f115fb497b61a8de977305984fPaul Westbrookimport android.content.ContentResolver; 212ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport android.content.res.AssetFileDescriptor; 222ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport android.graphics.Bitmap; 232ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport android.graphics.BitmapFactory; 24986d7a633359c1f115fb497b61a8de977305984fPaul Westbrookimport android.graphics.Matrix; 252ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport android.net.Uri; 262ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport android.os.AsyncTask; 2749d9fbc062289cdcf5ce3c57cc41927c8213c40aMark Weiimport android.util.DisplayMetrics; 282ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 29986d7a633359c1f115fb497b61a8de977305984fPaul Westbrookimport com.android.ex.photo.util.Exif; 301aee17e324f204080baaabceb64f4edf73681542Mark Weiimport com.android.ex.photo.util.ImageUtils; 31986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook 322ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport com.android.mail.providers.Attachment; 33b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag; 342ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport com.android.mail.utils.LogUtils; 352ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 362ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinimport java.io.IOException; 37986d7a633359c1f115fb497b61a8de977305984fPaul Westbrookimport java.io.InputStream; 382ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 392ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein/** 402ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * Performs the load of a thumbnail bitmap in a background 412ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * {@link AsyncTask}. Available for use with any view that implements 422ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein * the {@link AttachmentBitmapHolder} interface. 432ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein */ 442ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sappersteinpublic class ThumbnailLoadTask extends AsyncTask<Uri, Void, Bitmap> { 45b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook private static final String LOG_TAG = LogTag.getLogTag(); 462ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 472ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein private final AttachmentBitmapHolder mHolder; 482ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein private final int mWidth; 492ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein private final int mHeight; 502ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 512ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein public static void setupThumbnailPreview( 521dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein ThumbnailLoadTask task, final AttachmentBitmapHolder holder, 531dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final Attachment attachment, final Attachment prevAttachment) { 541dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int width = holder.getThumbnailWidth(); 551dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int height = holder.getThumbnailHeight(); 561aee17e324f204080baaabceb64f4edf73681542Mark Wei if (attachment == null || width == 0 || height == 0 578750066b10f80e2a8080016973b3296d76d18266Mark Wei || !ImageUtils.isImageMimeType(attachment.getContentType())) { 582ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein holder.setThumbnailToDefault(); 592ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return; 602ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 612ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 624cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein final Uri thumbnailUri = attachment.thumbnailUri; 634cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein final Uri contentUri = attachment.contentUri; 6410f8c4f1ca3619e761d4d1f874891f6d9259ac7bMark Wei final Uri uri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 6510f8c4f1ca3619e761d4d1f874891f6d9259ac7bMark Wei final Uri prevUri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 662ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // begin loading a thumbnail if this is an image and either the thumbnail or the original 672ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // content is ready (and different from any existing image) 684cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein if ((thumbnailUri != null || contentUri != null) 694cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein && (holder.bitmapSetToDefault() || 704cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein prevUri == null || !uri.equals(prevUri))) { 712ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // cancel/dispose any existing task and start a new one 722ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein if (task != null) { 732ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein task.cancel(true); 742ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 751dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein 762ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein task = new ThumbnailLoadTask( 771dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein holder, width, height); 784cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein task.execute(thumbnailUri, contentUri); 794cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } else if (thumbnailUri == null && contentUri == null) { 802ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // not an image, or no thumbnail exists. fall back to default. 812ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // async image load must separately ensure the default appears upon load failure. 822ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein holder.setThumbnailToDefault(); 832ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 842ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 852ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 862ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) { 872ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHolder = holder; 882ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mWidth = width; 892ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHeight = height; 902ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 912ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 922ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein @Override 932ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein protected Bitmap doInBackground(Uri... params) { 944cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein Bitmap result = loadBitmap(params[0]); 954cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein if (result == null) { 964cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein result = loadBitmap(params[1]); 974cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } 984cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein 994cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein return result; 1004cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } 1012ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1024cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein private Bitmap loadBitmap(final Uri thumbnailUri) { 103f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook if (thumbnailUri == null) { 104f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook LogUtils.e(LOG_TAG, "Attempting to load bitmap for null uri"); 105f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook return null; 106f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook } 107f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook 108e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final int orientation = getOrientation(thumbnailUri); 109986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook 110e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei AssetFileDescriptor fd = null; 111e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 112e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd = mHolder.getResolver().openAssetFileDescriptor(thumbnailUri, "r"); 113e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (isCancelled() || fd == null) { 1142ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return null; 1152ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1162ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1172ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein final BitmapFactory.Options opts = new BitmapFactory.Options(); 1182ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.inJustDecodeBounds = true; 119f73d6b25e5f38b85a5a4cad014017c7250c2d055Mark Wei opts.inDensity = DisplayMetrics.DENSITY_LOW; 1202ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 121e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts); 1222ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) { 1232ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return null; 1242ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1252ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1262ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.inJustDecodeBounds = false; 1271dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // Shrink both X and Y (but do not over-shrink) 1281dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // and pick the least affected dimension to ensure the thumbnail is fillable 1291dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // (i.e. ScaleType.CENTER_CROP) 1301dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int wDivider = Math.max(opts.outWidth / mWidth, 1); 1311dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int hDivider = Math.max(opts.outHeight / mHeight, 1); 1321dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein opts.inSampleSize = Math.min(wDivider, hDivider); 1332ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1342ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d", 1352ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize); 1362ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 137e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final Bitmap originalBitmap = BitmapFactory.decodeFileDescriptor( 138e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd.getFileDescriptor(), null, opts); 139986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook if (originalBitmap != null && orientation != 0) { 140986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook final Matrix matrix = new Matrix(); 141986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook matrix.postRotate(orientation); 142986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 143986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook originalBitmap.getHeight(), matrix, true); 144986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 145986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook return originalBitmap; 1462ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } catch (Throwable t) { 147e56653086586c02cda55474e537ec5502adce06fMartin Hibdon LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri, 148e56653086586c02cda55474e537ec5502adce06fMartin Hibdon t.getClass(), t.getMessage()); 1492ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } finally { 150e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (fd != null) { 151e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 152e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd.close(); 153e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } catch (IOException e) { 154e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei LogUtils.e(LOG_TAG, e, ""); 155e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 156e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 157e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 158e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 159e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return null; 160e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 161e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 162e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei private int getOrientation(final Uri thumbnailUri) { 163e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (thumbnailUri == null) { 164e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return 0; 165e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 166e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 167e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei InputStream in = null; 168e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 169e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final ContentResolver resolver = mHolder.getResolver(); 170e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei in = resolver.openInputStream(thumbnailUri); 1717b1e10093f127dd3fcc00a7d1a3c5d717a0f1deaMark Wei return Exif.getOrientation(in, -1); 172e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } catch (Throwable t) { 173e56653086586c02cda55474e537ec5502adce06fMartin Hibdon LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri, 174e56653086586c02cda55474e537ec5502adce06fMartin Hibdon t.getClass(), t.getMessage()); 175e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } finally { 176986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook if (in != null) { 177986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook try { 178986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook in.close(); 179986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } catch (IOException e) { 180986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook LogUtils.e(LOG_TAG, e, "error attemtping to close input stream"); 181986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 182986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 1832ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1842ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 185e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return 0; 1862ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1872ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1882ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein @Override 1892ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein protected void onPostExecute(Bitmap result) { 1902ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein if (result == null) { 1911aee17e324f204080baaabceb64f4edf73681542Mark Wei LogUtils.d(LOG_TAG, "back in UI thread, decode failed or file does not exist"); 1921aee17e324f204080baaabceb64f4edf73681542Mark Wei mHolder.thumbnailLoadFailed(); 1932ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return; 1942ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1952ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1962ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(), 1972ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein result.getHeight()); 1982ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHolder.setThumbnail(result); 1992ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 2002ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 2012ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein} 202