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