Home | History | Annotate | Download | only in ui
      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(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 
     89     public ThumbnailLoadTask(AttachmentBitmapHolder holder, int width, int height) {
     90         mHolder = holder;
     91         mWidth = width;
     92         mHeight = height;
     93     }
     94 
     95     @Override
     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 
    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 
    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
    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