1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.ui; 19 20 import android.content.ContentResolver; 21 import android.content.res.AssetFileDescriptor; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.graphics.Matrix; 25 import android.net.Uri; 26 import android.os.AsyncTask; 27 import android.util.DisplayMetrics; 28 29 import com.android.ex.photo.util.Exif; 30 import com.android.ex.photo.util.ImageUtils; 31 32 import com.android.mail.providers.Attachment; 33 import com.android.mail.utils.LogTag; 34 import com.android.mail.utils.LogUtils; 35 36 import java.io.IOException; 37 import java.io.InputStream; 38 39 /** 40 * Performs the load of a thumbnail bitmap in a background 41 * {@link AsyncTask}. Available for use with any view that implements 42 * the {@link AttachmentBitmapHolder} interface. 43 */ 44 public class ThumbnailLoadTask extends AsyncTask<Uri, Void, Bitmap> { 45 private static final String LOG_TAG = LogTag.getLogTag(); 46 47 private final AttachmentBitmapHolder mHolder; 48 private final int mWidth; 49 private final int mHeight; 50 setupThumbnailPreview(final AttachmentBitmapHolder holder, final Attachment attachment, final Attachment prevAttachment)51 public static void setupThumbnailPreview(final AttachmentBitmapHolder holder, 52 final Attachment attachment, final Attachment prevAttachment) { 53 final int width = holder.getThumbnailWidth(); 54 final int height = holder.getThumbnailHeight(); 55 if (attachment == null || width == 0 || height == 0 56 || !ImageUtils.isImageMimeType(attachment.getContentType())) { 57 holder.setThumbnailToDefault(); 58 return; 59 } 60 61 final Uri thumbnailUri = attachment.thumbnailUri; 62 final Uri contentUri = attachment.contentUri; 63 final Uri uri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 64 final Uri prevUri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 65 // begin loading a thumbnail if this is an image and either the thumbnail or the original 66 // content is ready (and different from any existing image) 67 if ((thumbnailUri != null || contentUri != null) 68 && (holder.bitmapSetToDefault() || 69 prevUri == null || !uri.equals(prevUri))) { 70 final ThumbnailLoadTask task = new ThumbnailLoadTask( 71 holder, width, height); 72 task.execute(thumbnailUri, contentUri); 73 } else if (thumbnailUri == null && contentUri == null) { 74 // not an image, or no thumbnail exists. fall back to default. 75 // async image load must separately ensure the default appears upon load failure. 76 holder.setThumbnailToDefault(); 77 } 78 } 79 ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height)80 public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) { 81 mHolder = holder; 82 mWidth = width; 83 mHeight = height; 84 } 85 86 @Override doInBackground(Uri... params)87 protected Bitmap doInBackground(Uri... params) { 88 Bitmap result = loadBitmap(params[0]); 89 if (result == null) { 90 result = loadBitmap(params[1]); 91 } 92 93 return result; 94 } 95 loadBitmap(final Uri thumbnailUri)96 private Bitmap loadBitmap(final Uri thumbnailUri) { 97 if (thumbnailUri == null) { 98 LogUtils.e(LOG_TAG, "Attempting to load bitmap for null uri"); 99 return null; 100 } 101 102 final int orientation = getOrientation(thumbnailUri); 103 104 AssetFileDescriptor fd = null; 105 try { 106 fd = mHolder.getResolver().openAssetFileDescriptor(thumbnailUri, "r"); 107 if (isCancelled() || fd == null) { 108 return null; 109 } 110 111 final BitmapFactory.Options opts = new BitmapFactory.Options(); 112 opts.inJustDecodeBounds = true; 113 opts.inDensity = DisplayMetrics.DENSITY_LOW; 114 115 BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts); 116 if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) { 117 return null; 118 } 119 120 opts.inJustDecodeBounds = false; 121 // Shrink both X and Y (but do not over-shrink) 122 // and pick the least affected dimension to ensure the thumbnail is fillable 123 // (i.e. ScaleType.CENTER_CROP) 124 final int wDivider = Math.max(opts.outWidth / mWidth, 1); 125 final int hDivider = Math.max(opts.outHeight / mHeight, 1); 126 opts.inSampleSize = Math.min(wDivider, hDivider); 127 128 LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d", 129 opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize); 130 131 final Bitmap originalBitmap = BitmapFactory.decodeFileDescriptor( 132 fd.getFileDescriptor(), null, opts); 133 if (originalBitmap != null && orientation != 0) { 134 final Matrix matrix = new Matrix(); 135 matrix.postRotate(orientation); 136 return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 137 originalBitmap.getHeight(), matrix, true); 138 } 139 return originalBitmap; 140 } catch (Throwable t) { 141 LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri, 142 t.getClass(), t.getMessage()); 143 } finally { 144 if (fd != null) { 145 try { 146 fd.close(); 147 } catch (IOException e) { 148 LogUtils.e(LOG_TAG, e, ""); 149 } 150 } 151 } 152 153 return null; 154 } 155 getOrientation(final Uri thumbnailUri)156 private int getOrientation(final Uri thumbnailUri) { 157 if (thumbnailUri == null) { 158 return 0; 159 } 160 161 InputStream in = null; 162 try { 163 final ContentResolver resolver = mHolder.getResolver(); 164 in = resolver.openInputStream(thumbnailUri); 165 return Exif.getOrientation(in, -1); 166 } catch (Throwable t) { 167 LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri, 168 t.getClass(), t.getMessage()); 169 } finally { 170 if (in != null) { 171 try { 172 in.close(); 173 } catch (IOException e) { 174 LogUtils.e(LOG_TAG, e, "error attemtping to close input stream"); 175 } 176 } 177 } 178 179 return 0; 180 } 181 182 @Override onPostExecute(Bitmap result)183 protected void onPostExecute(Bitmap result) { 184 if (result == null) { 185 LogUtils.d(LOG_TAG, "back in UI thread, decode failed or file does not exist"); 186 mHolder.thumbnailLoadFailed(); 187 return; 188 } 189 190 LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(), 191 result.getHeight()); 192 mHolder.setThumbnail(result); 193 } 194 195 } 196