• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common.BitmapUtils;
29 import com.android.gallery3d.common.Utils;
30 import com.android.gallery3d.util.ThreadPool.CancelListener;
31 import com.android.gallery3d.util.ThreadPool.Job;
32 import com.android.gallery3d.util.ThreadPool.JobContext;
33 
34 import java.io.FileInputStream;
35 import java.io.FileNotFoundException;
36 import java.io.InputStream;
37 import java.net.URI;
38 import java.net.URL;
39 
40 public 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 
UriImage(GalleryApp application, Path path, Uri uri, String contentType)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
requestImage(int type)68     public Job<Bitmap> requestImage(int type) {
69         return new BitmapJob(type);
70     }
71 
72     @Override
requestLargeImage()73     public Job<BitmapRegionDecoder> requestLargeImage() {
74         return new RegionDecoderJob();
75     }
76 
openFileOrDownloadTempFile(JobContext jc)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 
openOrDownloadInner(JobContext jc)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 
prepareInputFile(JobContext jc)135     private boolean prepareInputFile(JobContext jc) {
136         jc.setCancelListener(new CancelListener() {
137             public void onCancel() {
138                 synchronized (this) {
139                     notifyAll();
140                 }
141             }
142         });
143 
144         while (true) {
145             synchronized (this) {
146                 if (jc.isCancelled()) return false;
147                 if (mState == STATE_INIT) {
148                     mState = STATE_DOWNLOADING;
149                     // Then leave the synchronized block and continue.
150                 } else if (mState == STATE_ERROR) {
151                     return false;
152                 } else if (mState == STATE_DOWNLOADED) {
153                     return true;
154                 } else /* if (mState == STATE_DOWNLOADING) */ {
155                     try {
156                         wait();
157                     } catch (InterruptedException ex) {
158                         // ignored.
159                     }
160                     continue;
161                 }
162             }
163             // This is only reached for STATE_INIT->STATE_DOWNLOADING
164             openFileOrDownloadTempFile(jc);
165         }
166     }
167 
168     private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
run(JobContext jc)169         public BitmapRegionDecoder run(JobContext jc) {
170             if (!prepareInputFile(jc)) return null;
171             BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
172                     jc, mFileDescriptor.getFileDescriptor(), false);
173             mWidth = decoder.getWidth();
174             mHeight = decoder.getHeight();
175             return decoder;
176         }
177     }
178 
179     private class BitmapJob implements Job<Bitmap> {
180         private int mType;
181 
BitmapJob(int type)182         protected BitmapJob(int type) {
183             mType = type;
184         }
185 
186         @Override
run(JobContext jc)187         public Bitmap run(JobContext jc) {
188             if (!prepareInputFile(jc)) return null;
189             int targetSize = MediaItem.getTargetSize(mType);
190             Options options = new Options();
191             options.inPreferredConfig = Config.ARGB_8888;
192             Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
193                     mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
194 
195             if (jc.isCancelled() || bitmap == null) {
196                 return null;
197             }
198 
199             if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
200                 bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
201             } else {
202                 bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
203             }
204             return bitmap;
205         }
206     }
207 
208     @Override
getSupportedOperations()209     public int getSupportedOperations() {
210         int supported = SUPPORT_EDIT | SUPPORT_SETAS;
211         if (isSharable()) supported |= SUPPORT_SHARE;
212         if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) {
213             supported |= SUPPORT_FULL_IMAGE;
214         }
215         return supported;
216     }
217 
isSharable()218     private boolean isSharable() {
219         // We cannot grant read permission to the receiver since we put
220         // the data URI in EXTRA_STREAM instead of the data part of an intent
221         // And there are issues in MediaUploader and Bluetooth file sender to
222         // share a general image data. So, we only share for local file.
223         return ContentResolver.SCHEME_FILE.equals(mUri.getScheme());
224     }
225 
226     @Override
getMediaType()227     public int getMediaType() {
228         return MEDIA_TYPE_IMAGE;
229     }
230 
231     @Override
getContentUri()232     public Uri getContentUri() {
233         return mUri;
234     }
235 
236     @Override
getDetails()237     public MediaDetails getDetails() {
238         MediaDetails details = super.getDetails();
239         if (mWidth != 0 && mHeight != 0) {
240             details.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
241             details.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
242         }
243         if (mContentType != null) {
244             details.addDetail(MediaDetails.INDEX_MIMETYPE, mContentType);
245         }
246         if (ContentResolver.SCHEME_FILE.equals(mUri.getScheme())) {
247             String filePath = mUri.getPath();
248             details.addDetail(MediaDetails.INDEX_PATH, filePath);
249             MediaDetails.extractExifInfo(details, filePath);
250         }
251         return details;
252     }
253 
254     @Override
getMimeType()255     public String getMimeType() {
256         return mContentType;
257     }
258 
259     @Override
finalize()260     protected void finalize() throws Throwable {
261         try {
262             if (mFileDescriptor != null) {
263                 Utils.closeSilently(mFileDescriptor);
264             }
265         } finally {
266             super.finalize();
267         }
268     }
269 
270     @Override
getWidth()271     public int getWidth() {
272         return 0;
273     }
274 
275     @Override
getHeight()276     public int getHeight() {
277         return 0;
278     }
279 
280     @Override
getRotation()281     public int getRotation() {
282         return mRotation;
283     }
284 }
285