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