UriImage.java revision f9a0a4306d589b4a4e20554fed512a603426bfa1
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
17package com.android.gallery3d.data;
18
19import com.android.gallery3d.app.GalleryApp;
20import com.android.gallery3d.common.BitmapUtils;
21import com.android.gallery3d.common.Utils;
22import com.android.gallery3d.util.ThreadPool.CancelListener;
23import com.android.gallery3d.util.ThreadPool.Job;
24import com.android.gallery3d.util.ThreadPool.JobContext;
25
26import android.content.ContentResolver;
27import android.graphics.Bitmap;
28import android.graphics.Bitmap.Config;
29import android.graphics.BitmapFactory.Options;
30import android.graphics.BitmapRegionDecoder;
31import android.net.Uri;
32import android.os.ParcelFileDescriptor;
33import android.webkit.MimeTypeMap;
34
35import java.io.FileNotFoundException;
36import java.net.URI;
37import java.net.URL;
38
39public class UriImage extends MediaItem {
40    private static final String TAG = "UriImage";
41
42    private static final int STATE_INIT = 0;
43    private static final int STATE_DOWNLOADING = 1;
44    private static final int STATE_DOWNLOADED = 2;
45    private static final int STATE_ERROR = -1;
46
47    private final Uri mUri;
48    private final String mContentType;
49
50    private DownloadCache.Entry mCacheEntry;
51    private ParcelFileDescriptor mFileDescriptor;
52    private int mState = STATE_INIT;
53    private int mWidth;
54    private int mHeight;
55
56    private GalleryApp mApplication;
57
58    public UriImage(GalleryApp application, Path path, Uri uri) {
59        super(path, nextVersionNumber());
60        mUri = uri;
61        mApplication = Utils.checkNotNull(application);
62        mContentType = getMimeType(uri);
63    }
64
65    private String getMimeType(Uri uri) {
66        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
67            String extension =
68                    MimeTypeMap.getFileExtensionFromUrl(uri.toString());
69            String type = MimeTypeMap.getSingleton()
70                    .getMimeTypeFromExtension(extension);
71            if (type != null) return type;
72        }
73        return mApplication.getContentResolver().getType(uri);
74    }
75
76    @Override
77    public Job<Bitmap> requestImage(int type) {
78        return new BitmapJob(type);
79    }
80
81    @Override
82    public Job<BitmapRegionDecoder> requestLargeImage() {
83        return new RegionDecoderJob();
84    }
85
86    private void openFileOrDownloadTempFile(JobContext jc) {
87        int state = openOrDownloadInner(jc);
88        synchronized (this) {
89            mState = state;
90            if (mState != STATE_DOWNLOADED) {
91                if (mFileDescriptor != null) {
92                    Utils.closeSilently(mFileDescriptor);
93                    mFileDescriptor = null;
94                }
95            }
96            notifyAll();
97        }
98    }
99
100    private int openOrDownloadInner(JobContext jc) {
101        String scheme = mUri.getScheme();
102        if (ContentResolver.SCHEME_CONTENT.equals(scheme)
103                || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
104                || ContentResolver.SCHEME_FILE.equals(scheme)) {
105            try {
106                mFileDescriptor = mApplication.getContentResolver()
107                        .openFileDescriptor(mUri, "r");
108                if (jc.isCancelled()) return STATE_INIT;
109                return STATE_DOWNLOADED;
110            } catch (FileNotFoundException e) {
111                Log.w(TAG, "fail to open: " + mUri, e);
112                return STATE_ERROR;
113            }
114        } else {
115            try {
116                URL url = new URI(mUri.toString()).toURL();
117                mCacheEntry = mApplication.getDownloadCache().download(jc, url);
118                if (jc.isCancelled()) return STATE_INIT;
119                if (mCacheEntry == null) {
120                    Log.w(TAG, "download failed " + url);
121                    return STATE_ERROR;
122                }
123                mFileDescriptor = ParcelFileDescriptor.open(
124                        mCacheEntry.cacheFile, ParcelFileDescriptor.MODE_READ_ONLY);
125                return STATE_DOWNLOADED;
126            } catch (Throwable t) {
127                Log.w(TAG, "download error", t);
128                return STATE_ERROR;
129            }
130        }
131    }
132
133    private boolean prepareInputFile(JobContext jc) {
134        jc.setCancelListener(new CancelListener() {
135            public void onCancel() {
136                synchronized (this) {
137                    notifyAll();
138                }
139            }
140        });
141
142        while (true) {
143            synchronized (this) {
144                if (jc.isCancelled()) return false;
145                if (mState == STATE_INIT) {
146                    mState = STATE_DOWNLOADING;
147                    // Then leave the synchronized block and continue.
148                } else if (mState == STATE_ERROR) {
149                    return false;
150                } else if (mState == STATE_DOWNLOADED) {
151                    return true;
152                } else /* if (mState == STATE_DOWNLOADING) */ {
153                    try {
154                        wait();
155                    } catch (InterruptedException ex) {
156                        // ignored.
157                    }
158                    continue;
159                }
160            }
161            // This is only reached for STATE_INIT->STATE_DOWNLOADING
162            openFileOrDownloadTempFile(jc);
163        }
164    }
165
166    private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
167        public BitmapRegionDecoder run(JobContext jc) {
168            if (!prepareInputFile(jc)) return null;
169            BitmapRegionDecoder decoder = DecodeUtils.requestCreateBitmapRegionDecoder(
170                    jc, mFileDescriptor.getFileDescriptor(), false);
171            mWidth = decoder.getWidth();
172            mHeight = decoder.getHeight();
173            return decoder;
174        }
175    }
176
177    private class BitmapJob implements Job<Bitmap> {
178        private int mType;
179
180        protected BitmapJob(int type) {
181            mType = type;
182        }
183
184        public Bitmap run(JobContext jc) {
185            if (!prepareInputFile(jc)) return null;
186            int targetSize = LocalImage.getTargetSize(mType);
187            Options options = new Options();
188            options.inPreferredConfig = Config.ARGB_8888;
189            Bitmap bitmap = DecodeUtils.requestDecode(jc,
190                    mFileDescriptor.getFileDescriptor(), options, targetSize);
191            if (jc.isCancelled() || bitmap == null) {
192                return null;
193            }
194
195            if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
196                bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
197                        targetSize, true);
198            } else {
199                bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
200                        targetSize, true);
201            }
202
203            return bitmap;
204        }
205    }
206
207    @Override
208    public int getSupportedOperations() {
209        int supported = SUPPORT_EDIT | SUPPORT_SETAS;
210        if (isSharable()) supported |= SUPPORT_SHARE;
211        if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) {
212            supported |= SUPPORT_FULL_IMAGE;
213        }
214        return supported;
215    }
216
217    private boolean isSharable() {
218        // We cannot grant read permission to the receiver since we put
219        // the data URI in EXTRA_STREAM instead of the data part of an intent
220        // And there are issues in MediaUploader and Bluetooth file sender to
221        // share a general image data. So, we only share for local file.
222        return ContentResolver.SCHEME_FILE.equals(mUri.getScheme());
223    }
224
225    @Override
226    public int getMediaType() {
227        return MEDIA_TYPE_IMAGE;
228    }
229
230    @Override
231    public Uri getContentUri() {
232        return mUri;
233    }
234
235    @Override
236    public MediaDetails getDetails() {
237        MediaDetails details = super.getDetails();
238        if (mWidth != 0 && mHeight != 0) {
239            details.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
240            details.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
241        }
242        details.addDetail(MediaDetails.INDEX_MIMETYPE, mContentType);
243        if (ContentResolver.SCHEME_FILE.equals(mUri.getScheme())) {
244            String filePath = mUri.getPath();
245            details.addDetail(MediaDetails.INDEX_PATH, filePath);
246            MediaDetails.extractExifInfo(details, filePath);
247        }
248        return details;
249    }
250
251    @Override
252    public String getMimeType() {
253        return mContentType;
254    }
255
256    @Override
257    protected void finalize() throws Throwable {
258        try {
259            if (mFileDescriptor != null) {
260                Utils.closeSilently(mFileDescriptor);
261            }
262        } finally {
263            super.finalize();
264        }
265    }
266}
267