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