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