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 512e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao public static void setupThumbnailPreview(AttachmentTile.AttachmentPreviewCache cache, 522e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao AttachmentBitmapHolder holder, Attachment attachment, Attachment prevAttachment) { 532e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao // Check cache first 542e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao if (cache != null) { 552e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao final Bitmap cached = cache.get(attachment); 562e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao if (cached != null) { 572e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao holder.setThumbnail(cached); 582e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao return; 592e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao } 602e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao } 612e607aebf60ae43413d244548a7e41edf681e4a7Jin Cao 621dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int width = holder.getThumbnailWidth(); 631dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int height = holder.getThumbnailHeight(); 641aee17e324f204080baaabceb64f4edf73681542Mark Wei if (attachment == null || width == 0 || height == 0 658750066b10f80e2a8080016973b3296d76d18266Mark Wei || !ImageUtils.isImageMimeType(attachment.getContentType())) { 662ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein holder.setThumbnailToDefault(); 672ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return; 682ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 692ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 704cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein final Uri thumbnailUri = attachment.thumbnailUri; 714cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein final Uri contentUri = attachment.contentUri; 72c18cd49e5c8684d9d8262f34f426116c3e1d7e23Jin Cao final Uri uri = attachment.getIdentifierUri(); 7310f8c4f1ca3619e761d4d1f874891f6d9259ac7bMark Wei final Uri prevUri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 742ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // begin loading a thumbnail if this is an image and either the thumbnail or the original 752ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // content is ready (and different from any existing image) 764cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein if ((thumbnailUri != null || contentUri != null) 774cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein && (holder.bitmapSetToDefault() || 784cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein prevUri == null || !uri.equals(prevUri))) { 796c9771c0966a9aded0706c363233bbd3efaa5a9cAndrew Sapperstein final ThumbnailLoadTask task = new ThumbnailLoadTask( 801dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein holder, width, height); 814cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein task.execute(thumbnailUri, contentUri); 824cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } else if (thumbnailUri == null && contentUri == null) { 832ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // not an image, or no thumbnail exists. fall back to default. 842ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein // async image load must separately ensure the default appears upon load failure. 852ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein holder.setThumbnailToDefault(); 862ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 872ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 882ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 892ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) { 902ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHolder = holder; 912ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mWidth = width; 922ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHeight = height; 932ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 942ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 952ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein @Override 962ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein protected Bitmap doInBackground(Uri... params) { 974cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein Bitmap result = loadBitmap(params[0]); 984cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein if (result == null) { 994cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein result = loadBitmap(params[1]); 1004cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } 1014cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein 1024cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein return result; 1034cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein } 1042ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1054cb51dbce9635f4bf68a6de88f759e2f86d20325Andrew Sapperstein private Bitmap loadBitmap(final Uri thumbnailUri) { 106f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook if (thumbnailUri == null) { 107f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook LogUtils.e(LOG_TAG, "Attempting to load bitmap for null uri"); 108f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook return null; 109f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook } 110f3791ebe6ca60a795908fb0db6b00b1b1f74f2fdPaul Westbrook 111e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final int orientation = getOrientation(thumbnailUri); 112986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook 113e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei AssetFileDescriptor fd = null; 114e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 115e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd = mHolder.getResolver().openAssetFileDescriptor(thumbnailUri, "r"); 116e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (isCancelled() || fd == null) { 1172ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return null; 1182ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1192ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1202ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein final BitmapFactory.Options opts = new BitmapFactory.Options(); 1212ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.inJustDecodeBounds = true; 122f73d6b25e5f38b85a5a4cad014017c7250c2d055Mark Wei opts.inDensity = DisplayMetrics.DENSITY_LOW; 1232ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 124e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts); 1252ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) { 1262ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return null; 1272ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1282ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1292ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.inJustDecodeBounds = false; 1301dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // Shrink both X and Y (but do not over-shrink) 1311dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // and pick the least affected dimension to ensure the thumbnail is fillable 1321dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein // (i.e. ScaleType.CENTER_CROP) 1331dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int wDivider = Math.max(opts.outWidth / mWidth, 1); 1341dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein final int hDivider = Math.max(opts.outHeight / mHeight, 1); 1351dc25e45b43aaab2ed869381de1b1276cf72b08fAndrew Sapperstein opts.inSampleSize = Math.min(wDivider, hDivider); 1362ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1372ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d", 1382ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize); 1392ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 140e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final Bitmap originalBitmap = BitmapFactory.decodeFileDescriptor( 141e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd.getFileDescriptor(), null, opts); 142986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook if (originalBitmap != null && orientation != 0) { 143986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook final Matrix matrix = new Matrix(); 144986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook matrix.postRotate(orientation); 145986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 146986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook originalBitmap.getHeight(), matrix, true); 147986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 148986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook return originalBitmap; 1492ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } catch (Throwable t) { 150e56653086586c02cda55474e537ec5502adce06fMartin Hibdon LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri, 151e56653086586c02cda55474e537ec5502adce06fMartin Hibdon t.getClass(), t.getMessage()); 1522ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } finally { 153e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (fd != null) { 154e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 155e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei fd.close(); 156e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } catch (IOException e) { 157e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei LogUtils.e(LOG_TAG, e, ""); 158e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 159e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 160e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 161e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 162e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return null; 163e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 164e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 165e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei private int getOrientation(final Uri thumbnailUri) { 166e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei if (thumbnailUri == null) { 167e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return 0; 168e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } 169e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei 170e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei InputStream in = null; 171e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei try { 172e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei final ContentResolver resolver = mHolder.getResolver(); 173e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei in = resolver.openInputStream(thumbnailUri); 1747b1e10093f127dd3fcc00a7d1a3c5d717a0f1deaMark Wei return Exif.getOrientation(in, -1); 175e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } catch (Throwable t) { 176e56653086586c02cda55474e537ec5502adce06fMartin Hibdon LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri, 177e56653086586c02cda55474e537ec5502adce06fMartin Hibdon t.getClass(), t.getMessage()); 178e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei } finally { 179986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook if (in != null) { 180986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook try { 181986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook in.close(); 182986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } catch (IOException e) { 183986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook LogUtils.e(LOG_TAG, e, "error attemtping to close input stream"); 184986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 185986d7a633359c1f115fb497b61a8de977305984fPaul Westbrook } 1862ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1872ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 188e16b4dddf6a310e0e70908fdffd7bad45d97a99cMark Wei return 0; 1892ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1902ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1912ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein @Override 1922ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein protected void onPostExecute(Bitmap result) { 1932ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein if (result == null) { 1941aee17e324f204080baaabceb64f4edf73681542Mark Wei LogUtils.d(LOG_TAG, "back in UI thread, decode failed or file does not exist"); 1951aee17e324f204080baaabceb64f4edf73681542Mark Wei mHolder.thumbnailLoadFailed(); 1962ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein return; 1972ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 1982ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 1992ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(), 2002ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein result.getHeight()); 2012ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein mHolder.setThumbnail(result); 2022ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 2032ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 2042ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein} 205