Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.data;
     18 
     19 import android.content.ContentResolver;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Bitmap.Config;
     22 import android.graphics.BitmapFactory.Options;
     23 import android.graphics.BitmapRegionDecoder;
     24 import android.net.Uri;
     25 import android.os.ParcelFileDescriptor;
     26 
     27 import com.android.gallery3d.app.GalleryApp;
     28 import com.android.gallery3d.app.PanoramaMetadataSupport;
     29 import com.android.gallery3d.common.BitmapUtils;
     30 import com.android.gallery3d.common.Utils;
     31 import com.android.gallery3d.util.ThreadPool.CancelListener;
     32 import com.android.gallery3d.util.ThreadPool.Job;
     33 import com.android.gallery3d.util.ThreadPool.JobContext;
     34 
     35 import java.io.FileInputStream;
     36 import java.io.FileNotFoundException;
     37 import java.io.InputStream;
     38 import java.net.URI;
     39 import java.net.URL;
     40 
     41 public class UriImage extends MediaItem {
     42     private static final String TAG = "UriImage";
     43 
     44     private static final int STATE_INIT = 0;
     45     private static final int STATE_DOWNLOADING = 1;
     46     private static final int STATE_DOWNLOADED = 2;
     47     private static final int STATE_ERROR = -1;
     48 
     49     private final Uri mUri;
     50     private final String mContentType;
     51 
     52     private DownloadCache.Entry mCacheEntry;
     53     private ParcelFileDescriptor mFileDescriptor;
     54     private int mState = STATE_INIT;
     55     private int mWidth;
     56     private int mHeight;
     57     private int mRotation;
     58     private PanoramaMetadataSupport mPanoramaMetadata = new PanoramaMetadataSupport(this);
     59 
     60     private GalleryApp mApplication;
     61 
     62     public UriImage(GalleryApp application, Path path, Uri uri, String contentType) {
     63         super(path, nextVersionNumber());
     64         mUri = uri;
     65         mApplication = Utils.checkNotNull(application);
     66         mContentType = contentType;
     67     }
     68 
     69     @Override
     70     public Job<Bitmap> requestImage(int type) {
     71         return new BitmapJob(type);
     72     }
     73 
     74     @Override
     75     public Job<BitmapRegionDecoder> requestLargeImage() {
     76         return new RegionDecoderJob();
     77     }
     78 
     79     private void openFileOrDownloadTempFile(JobContext jc) {
     80         int state = openOrDownloadInner(jc);
     81         synchronized (this) {
     82             mState = state;
     83             if (mState != STATE_DOWNLOADED) {
     84                 if (mFileDescriptor != null) {
     85                     Utils.closeSilently(mFileDescriptor);
     86                     mFileDescriptor = null;
     87                 }
     88             }
     89             notifyAll();
     90         }
     91     }
     92 
     93     private int openOrDownloadInner(JobContext jc) {
     94         String scheme = mUri.getScheme();
     95         if (ContentResolver.SCHEME_CONTENT.equals(scheme)
     96                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
     97                 || ContentResolver.SCHEME_FILE.equals(scheme)) {
     98             try {
     99                 if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
    100                     InputStream is = mApplication.getContentResolver()
    101                             .openInputStream(mUri);
    102                     mRotation = Exif.getOrientation(is);
    103                     Utils.closeSilently(is);
    104                 }
    105                 mFileDescriptor = mApplication.getContentResolver()
    106                         .openFileDescriptor(mUri, "r");
    107                 if (jc.isCancelled()) return STATE_INIT;
    108                 return STATE_DOWNLOADED;
    109             } catch (FileNotFoundException e) {
    110                 Log.w(TAG, "fail to open: " + mUri, e);
    111                 return STATE_ERROR;
    112             }
    113         } else {
    114             try {
    115                 URL url = new URI(mUri.toString()).toURL();
    116                 mCacheEntry = mApplication.getDownloadCache().download(jc, url);
    117                 if (jc.isCancelled()) return STATE_INIT;
    118                 if (mCacheEntry == null) {
    119                     Log.w(TAG, "download failed " + url);
    120                     return STATE_ERROR;
    121                 }
    122                 if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
    123                     InputStream is = new FileInputStream(mCacheEntry.cacheFile);
    124                     mRotation = Exif.getOrientation(is);
    125                     Utils.closeSilently(is);
    126                 }
    127                 mFileDescriptor = ParcelFileDescriptor.open(
    128                         mCacheEntry.cacheFile, ParcelFileDescriptor.MODE_READ_ONLY);
    129                 return STATE_DOWNLOADED;
    130             } catch (Throwable t) {
    131                 Log.w(TAG, "download error", t);
    132                 return STATE_ERROR;
    133             }
    134         }
    135     }
    136 
    137     private boolean prepareInputFile(JobContext jc) {
    138         jc.setCancelListener(new CancelListener() {
    139             @Override
    140             public void onCancel() {
    141                 synchronized (this) {
    142                     notifyAll();
    143                 }
    144             }
    145         });
    146 
    147         while (true) {
    148             synchronized (this) {
    149                 if (jc.isCancelled()) return false;
    150                 if (mState == STATE_INIT) {
    151                     mState = STATE_DOWNLOADING;
    152                     // Then leave the synchronized block and continue.
    153                 } else if (mState == STATE_ERROR) {
    154                     return false;
    155                 } else if (mState == STATE_DOWNLOADED) {
    156                     return true;
    157                 } else /* if (mState == STATE_DOWNLOADING) */ {
    158                     try {
    159                         wait();
    160                     } catch (InterruptedException ex) {
    161                         // ignored.
    162                     }
    163                     continue;
    164                 }
    165             }
    166             // This is only reached for STATE_INIT->STATE_DOWNLOADING
    167             openFileOrDownloadTempFile(jc);
    168         }
    169     }
    170 
    171     private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
    172         @Override
    173         public BitmapRegionDecoder run(JobContext jc) {
    174             if (!prepareInputFile(jc)) return null;
    175             BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
    176                     jc, mFileDescriptor.getFileDescriptor(), false);
    177             mWidth = decoder.getWidth();
    178             mHeight = decoder.getHeight();
    179             return decoder;
    180         }
    181     }
    182 
    183     private class BitmapJob implements Job<Bitmap> {
    184         private int mType;
    185 
    186         protected BitmapJob(int type) {
    187             mType = type;
    188         }
    189 
    190         @Override
    191         public Bitmap run(JobContext jc) {
    192             if (!prepareInputFile(jc)) return null;
    193             int targetSize = MediaItem.getTargetSize(mType);
    194             Options options = new Options();
    195             options.inPreferredConfig = Config.ARGB_8888;
    196             Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
    197                     mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
    198 
    199             if (jc.isCancelled() || bitmap == null) {
    200                 return null;
    201             }
    202 
    203             if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
    204                 bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
    205             } else {
    206                 bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
    207             }
    208             return bitmap;
    209         }
    210     }
    211 
    212     @Override
    213     public int getSupportedOperations() {
    214         int supported = SUPPORT_EDIT | SUPPORT_SETAS;
    215         if (isSharable()) supported |= SUPPORT_SHARE;
    216         if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) {
    217             supported |= SUPPORT_FULL_IMAGE;
    218         }
    219         return supported;
    220     }
    221 
    222     @Override
    223     public void getPanoramaSupport(PanoramaSupportCallback callback) {
    224         mPanoramaMetadata.getPanoramaSupport(mApplication, callback);
    225     }
    226 
    227     @Override
    228     public void clearCachedPanoramaSupport() {
    229         mPanoramaMetadata.clearCachedValues();
    230     }
    231 
    232     private boolean isSharable() {
    233         // We cannot grant read permission to the receiver since we put
    234         // the data URI in EXTRA_STREAM instead of the data part of an intent
    235         // And there are issues in MediaUploader and Bluetooth file sender to
    236         // share a general image data. So, we only share for local file.
    237         return ContentResolver.SCHEME_FILE.equals(mUri.getScheme());
    238     }
    239 
    240     @Override
    241     public int getMediaType() {
    242         return MEDIA_TYPE_IMAGE;
    243     }
    244 
    245     @Override
    246     public Uri getContentUri() {
    247         return mUri;
    248     }
    249 
    250     @Override
    251     public MediaDetails getDetails() {
    252         MediaDetails details = super.getDetails();
    253         if (mWidth != 0 && mHeight != 0) {
    254             details.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
    255             details.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
    256         }
    257         if (mContentType != null) {
    258             details.addDetail(MediaDetails.INDEX_MIMETYPE, mContentType);
    259         }
    260         if (ContentResolver.SCHEME_FILE.equals(mUri.getScheme())) {
    261             String filePath = mUri.getPath();
    262             details.addDetail(MediaDetails.INDEX_PATH, filePath);
    263             MediaDetails.extractExifInfo(details, filePath);
    264         }
    265         return details;
    266     }
    267 
    268     @Override
    269     public String getMimeType() {
    270         return mContentType;
    271     }
    272 
    273     @Override
    274     protected void finalize() throws Throwable {
    275         try {
    276             if (mFileDescriptor != null) {
    277                 Utils.closeSilently(mFileDescriptor);
    278             }
    279         } finally {
    280             super.finalize();
    281         }
    282     }
    283 
    284     @Override
    285     public int getWidth() {
    286         return 0;
    287     }
    288 
    289     @Override
    290     public int getHeight() {
    291         return 0;
    292     }
    293 
    294     @Override
    295     public int getRotation() {
    296         return mRotation;
    297     }
    298 }
    299