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