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