UriImage.java revision 4bb5912e85f6d1bd8a6b78d6d52b4c4da7aeb740
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; 26import android.webkit.MimeTypeMap; 27 28import com.android.gallery3d.app.GalleryApp; 29import com.android.gallery3d.common.BitmapUtils; 30import com.android.gallery3d.common.Utils; 31import com.android.gallery3d.util.ThreadPool.CancelListener; 32import com.android.gallery3d.util.ThreadPool.Job; 33import com.android.gallery3d.util.ThreadPool.JobContext; 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.toLowerCase()); 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 (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) { 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 (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) { 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.createBitmapRegionDecoder( 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 @Override 199 public Bitmap run(JobContext jc) { 200 if (!prepareInputFile(jc)) return null; 201 int targetSize = MediaItem.getTargetSize(mType); 202 Options options = new Options(); 203 options.inPreferredConfig = Config.ARGB_8888; 204 Bitmap bitmap = DecodeUtils.decodeThumbnail(jc, 205 mFileDescriptor.getFileDescriptor(), options, targetSize, mType); 206 207 if (jc.isCancelled() || bitmap == null) { 208 return null; 209 } 210 211 if (mType == MediaItem.TYPE_MICROTHUMBNAIL) { 212 bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true); 213 } else { 214 bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true); 215 } 216 return bitmap; 217 } 218 } 219 220 @Override 221 public int getSupportedOperations() { 222 int supported = SUPPORT_EDIT | SUPPORT_SETAS; 223 if (isSharable()) supported |= SUPPORT_SHARE; 224 if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) { 225 supported |= SUPPORT_FULL_IMAGE; 226 } 227 return supported; 228 } 229 230 private boolean isSharable() { 231 // We cannot grant read permission to the receiver since we put 232 // the data URI in EXTRA_STREAM instead of the data part of an intent 233 // And there are issues in MediaUploader and Bluetooth file sender to 234 // share a general image data. So, we only share for local file. 235 return ContentResolver.SCHEME_FILE.equals(mUri.getScheme()); 236 } 237 238 @Override 239 public int getMediaType() { 240 return MEDIA_TYPE_IMAGE; 241 } 242 243 @Override 244 public Uri getContentUri() { 245 return mUri; 246 } 247 248 @Override 249 public MediaDetails getDetails() { 250 MediaDetails details = super.getDetails(); 251 if (mWidth != 0 && mHeight != 0) { 252 details.addDetail(MediaDetails.INDEX_WIDTH, mWidth); 253 details.addDetail(MediaDetails.INDEX_HEIGHT, mHeight); 254 } 255 if (mContentType != null) { 256 details.addDetail(MediaDetails.INDEX_MIMETYPE, mContentType); 257 } 258 if (ContentResolver.SCHEME_FILE.equals(mUri.getScheme())) { 259 String filePath = mUri.getPath(); 260 details.addDetail(MediaDetails.INDEX_PATH, filePath); 261 MediaDetails.extractExifInfo(details, filePath); 262 } 263 return details; 264 } 265 266 @Override 267 public String getMimeType() { 268 return mContentType; 269 } 270 271 @Override 272 protected void finalize() throws Throwable { 273 try { 274 if (mFileDescriptor != null) { 275 Utils.closeSilently(mFileDescriptor); 276 } 277 } finally { 278 super.finalize(); 279 } 280 } 281 282 @Override 283 public int getWidth() { 284 return 0; 285 } 286 287 @Override 288 public int getHeight() { 289 return 0; 290 } 291 292 @Override 293 public int getRotation() { 294 return mRotation; 295 } 296} 297