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