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