/* * Copyright (C) 2012 Google Inc. * Licensed to The Android Open Source Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mail.ui; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; import android.util.DisplayMetrics; import com.android.ex.photo.util.Exif; import com.android.ex.photo.util.ImageUtils; import com.android.mail.providers.Attachment; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; import java.io.IOException; import java.io.InputStream; /** * Performs the load of a thumbnail bitmap in a background * {@link AsyncTask}. Available for use with any view that implements * the {@link AttachmentBitmapHolder} interface. */ public class ThumbnailLoadTask extends AsyncTask { private static final String LOG_TAG = LogTag.getLogTag(); private final AttachmentBitmapHolder mHolder; private final int mWidth; private final int mHeight; public static void setupThumbnailPreview(AttachmentTile.AttachmentPreviewCache cache, AttachmentBitmapHolder holder, Attachment attachment, Attachment prevAttachment) { // Check cache first if (cache != null) { final Bitmap cached = cache.get(attachment); if (cached != null) { holder.setThumbnail(cached); return; } } final int width = holder.getThumbnailWidth(); final int height = holder.getThumbnailHeight(); if (attachment == null || width == 0 || height == 0 || !ImageUtils.isImageMimeType(attachment.getContentType())) { holder.setThumbnailToDefault(); return; } final Uri thumbnailUri = attachment.thumbnailUri; final Uri contentUri = attachment.contentUri; final Uri uri = attachment.getIdentifierUri(); final Uri prevUri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); // begin loading a thumbnail if this is an image and either the thumbnail or the original // content is ready (and different from any existing image) if ((thumbnailUri != null || contentUri != null) && (holder.bitmapSetToDefault() || prevUri == null || !uri.equals(prevUri))) { final ThumbnailLoadTask task = new ThumbnailLoadTask( holder, width, height); task.execute(thumbnailUri, contentUri); } else if (thumbnailUri == null && contentUri == null) { // not an image, or no thumbnail exists. fall back to default. // async image load must separately ensure the default appears upon load failure. holder.setThumbnailToDefault(); } } public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) { mHolder = holder; mWidth = width; mHeight = height; } @Override protected Bitmap doInBackground(Uri... params) { Bitmap result = loadBitmap(params[0]); if (result == null) { result = loadBitmap(params[1]); } return result; } private Bitmap loadBitmap(final Uri thumbnailUri) { if (thumbnailUri == null) { LogUtils.e(LOG_TAG, "Attempting to load bitmap for null uri"); return null; } final int orientation = getOrientation(thumbnailUri); AssetFileDescriptor fd = null; try { fd = mHolder.getResolver().openAssetFileDescriptor(thumbnailUri, "r"); if (isCancelled() || fd == null) { return null; } final BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; opts.inDensity = DisplayMetrics.DENSITY_LOW; BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts); if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) { return null; } opts.inJustDecodeBounds = false; // Shrink both X and Y (but do not over-shrink) // and pick the least affected dimension to ensure the thumbnail is fillable // (i.e. ScaleType.CENTER_CROP) final int wDivider = Math.max(opts.outWidth / mWidth, 1); final int hDivider = Math.max(opts.outHeight / mHeight, 1); opts.inSampleSize = Math.min(wDivider, hDivider); LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d", opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize); final Bitmap originalBitmap = BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, opts); if (originalBitmap != null && orientation != 0) { final Matrix matrix = new Matrix(); matrix.postRotate(orientation); return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), originalBitmap.getHeight(), matrix, true); } return originalBitmap; } catch (Throwable t) { LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri, t.getClass(), t.getMessage()); } finally { if (fd != null) { try { fd.close(); } catch (IOException e) { LogUtils.e(LOG_TAG, e, ""); } } } return null; } private int getOrientation(final Uri thumbnailUri) { if (thumbnailUri == null) { return 0; } InputStream in = null; try { final ContentResolver resolver = mHolder.getResolver(); in = resolver.openInputStream(thumbnailUri); return Exif.getOrientation(in, -1); } catch (Throwable t) { LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri, t.getClass(), t.getMessage()); } finally { if (in != null) { try { in.close(); } catch (IOException e) { LogUtils.e(LOG_TAG, e, "error attemtping to close input stream"); } } } return 0; } @Override protected void onPostExecute(Bitmap result) { if (result == null) { LogUtils.d(LOG_TAG, "back in UI thread, decode failed or file does not exist"); mHolder.thumbnailLoadFailed(); return; } LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(), result.getHeight()); mHolder.setThumbnail(result); } }