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 51 public static void setupThumbnailPreview( 52 ThumbnailLoadTask task, final AttachmentBitmapHolder holder, 53 final Attachment attachment, final Attachment prevAttachment) { 54 final int width = holder.getThumbnailWidth(); 55 final int height = holder.getThumbnailHeight(); 56 if (attachment == null || width == 0 || height == 0 57 || !ImageUtils.isImageMimeType(attachment.getContentType())) { 58 holder.setThumbnailToDefault(); 59 return; 60 } 61 62 final Uri thumbnailUri = attachment.thumbnailUri; 63 final Uri contentUri = attachment.contentUri; 64 final Uri uri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 65 final Uri prevUri = (prevAttachment == null) ? null : prevAttachment.getIdentifierUri(); 66 // begin loading a thumbnail if this is an image and either the thumbnail or the original 67 // content is ready (and different from any existing image) 68 if ((thumbnailUri != null || contentUri != null) 69 && (holder.bitmapSetToDefault() || 70 prevUri == null || !uri.equals(prevUri))) { 71 // cancel/dispose any existing task and start a new one 72 if (task != null) { 73 task.cancel(true); 74 } 75 76 task = new ThumbnailLoadTask( 77 holder, width, height); 78 task.execute(thumbnailUri, contentUri); 79 } else if (thumbnailUri == null && contentUri == null) { 80 // not an image, or no thumbnail exists. fall back to default. 81 // async image load must separately ensure the default appears upon load failure. 82 holder.setThumbnailToDefault(); 83 } 84 } 85 86 public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) { 87 mHolder = holder; 88 mWidth = width; 89 mHeight = height; 90 } 91 92 @Override 93 protected Bitmap doInBackground(Uri... params) { 94 Bitmap result = loadBitmap(params[0]); 95 if (result == null) { 96 result = loadBitmap(params[1]); 97 } 98 99 return result; 100 } 101 102 private Bitmap loadBitmap(final Uri thumbnailUri) { 103 if (thumbnailUri == null) { 104 LogUtils.e(LOG_TAG, "Attempting to load bitmap for null uri"); 105 return null; 106 } 107 108 final int orientation = getOrientation(thumbnailUri); 109 110 AssetFileDescriptor fd = null; 111 try { 112 fd = mHolder.getResolver().openAssetFileDescriptor(thumbnailUri, "r"); 113 if (isCancelled() || fd == null) { 114 return null; 115 } 116 117 final BitmapFactory.Options opts = new BitmapFactory.Options(); 118 opts.inJustDecodeBounds = true; 119 opts.inDensity = DisplayMetrics.DENSITY_LOW; 120 121 BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts); 122 if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) { 123 return null; 124 } 125 126 opts.inJustDecodeBounds = false; 127 // Shrink both X and Y (but do not over-shrink) 128 // and pick the least affected dimension to ensure the thumbnail is fillable 129 // (i.e. ScaleType.CENTER_CROP) 130 final int wDivider = Math.max(opts.outWidth / mWidth, 1); 131 final int hDivider = Math.max(opts.outHeight / mHeight, 1); 132 opts.inSampleSize = Math.min(wDivider, hDivider); 133 134 LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d", 135 opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize); 136 137 final Bitmap originalBitmap = BitmapFactory.decodeFileDescriptor( 138 fd.getFileDescriptor(), null, opts); 139 if (originalBitmap != null && orientation != 0) { 140 final Matrix matrix = new Matrix(); 141 matrix.postRotate(orientation); 142 return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 143 originalBitmap.getHeight(), matrix, true); 144 } 145 return originalBitmap; 146 } catch (Throwable t) { 147 LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri, 148 t.getClass(), t.getMessage()); 149 } finally { 150 if (fd != null) { 151 try { 152 fd.close(); 153 } catch (IOException e) { 154 LogUtils.e(LOG_TAG, e, ""); 155 } 156 } 157 } 158 159 return null; 160 } 161 162 private int getOrientation(final Uri thumbnailUri) { 163 if (thumbnailUri == null) { 164 return 0; 165 } 166 167 InputStream in = null; 168 try { 169 final ContentResolver resolver = mHolder.getResolver(); 170 in = resolver.openInputStream(thumbnailUri); 171 return Exif.getOrientation(in, -1); 172 } catch (Throwable t) { 173 LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri, 174 t.getClass(), t.getMessage()); 175 } finally { 176 if (in != null) { 177 try { 178 in.close(); 179 } catch (IOException e) { 180 LogUtils.e(LOG_TAG, e, "error attemtping to close input stream"); 181 } 182 } 183 } 184 185 return 0; 186 } 187 188 @Override 189 protected void onPostExecute(Bitmap result) { 190 if (result == null) { 191 LogUtils.d(LOG_TAG, "back in UI thread, decode failed or file does not exist"); 192 mHolder.thumbnailLoadFailed(); 193 return; 194 } 195 196 LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(), 197 result.getHeight()); 198 mHolder.setThumbnail(result); 199 } 200 201 } 202