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