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