blob: 737e85f25dd019da2833521e2e8f7c6437d98bda [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gallery3d.data;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.LightCycleHelper;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
public class UriImage extends MediaItem {
private static final String TAG = "UriImage";
private static final int STATE_INIT = 0;
private static final int STATE_DOWNLOADING = 1;
private static final int STATE_DOWNLOADED = 2;
private static final int STATE_ERROR = -1;
private final Uri mUri;
private final String mContentType;
private DownloadCache.Entry mCacheEntry;
private ParcelFileDescriptor mFileDescriptor;
private int mState = STATE_INIT;
private int mWidth;
private int mHeight;
private int mRotation;
private boolean mUsePanoramaViewer;
private boolean mUsePanoramaViewerInitialized;
private GalleryApp mApplication;
public UriImage(GalleryApp application, Path path, Uri uri, String contentType) {
super(path, nextVersionNumber());
mUri = uri;
mApplication = Utils.checkNotNull(application);
mContentType = contentType;
}
@Override
public Job<Bitmap> requestImage(int type) {
return new BitmapJob(type);
}
@Override
public Job<BitmapRegionDecoder> requestLargeImage() {
return new RegionDecoderJob();
}
private void openFileOrDownloadTempFile(JobContext jc) {
int state = openOrDownloadInner(jc);
synchronized (this) {
mState = state;
if (mState != STATE_DOWNLOADED) {
if (mFileDescriptor != null) {
Utils.closeSilently(mFileDescriptor);
mFileDescriptor = null;
}
}
notifyAll();
}
}
private int openOrDownloadInner(JobContext jc) {
String scheme = mUri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
try {
if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
InputStream is = mApplication.getContentResolver()
.openInputStream(mUri);
mRotation = Exif.getOrientation(is);
Utils.closeSilently(is);
}
mFileDescriptor = mApplication.getContentResolver()
.openFileDescriptor(mUri, "r");
if (jc.isCancelled()) return STATE_INIT;
return STATE_DOWNLOADED;
} catch (FileNotFoundException e) {
Log.w(TAG, "fail to open: " + mUri, e);
return STATE_ERROR;
}
} else {
try {
URL url = new URI(mUri.toString()).toURL();
mCacheEntry = mApplication.getDownloadCache().download(jc, url);
if (jc.isCancelled()) return STATE_INIT;
if (mCacheEntry == null) {
Log.w(TAG, "download failed " + url);
return STATE_ERROR;
}
if (MIME_TYPE_JPEG.equalsIgnoreCase(mContentType)) {
InputStream is = new FileInputStream(mCacheEntry.cacheFile);
mRotation = Exif.getOrientation(is);
Utils.closeSilently(is);
}
mFileDescriptor = ParcelFileDescriptor.open(
mCacheEntry.cacheFile, ParcelFileDescriptor.MODE_READ_ONLY);
return STATE_DOWNLOADED;
} catch (Throwable t) {
Log.w(TAG, "download error", t);
return STATE_ERROR;
}
}
}
private boolean prepareInputFile(JobContext jc) {
jc.setCancelListener(new CancelListener() {
@Override
public void onCancel() {
synchronized (this) {
notifyAll();
}
}
});
while (true) {
synchronized (this) {
if (jc.isCancelled()) return false;
if (mState == STATE_INIT) {
mState = STATE_DOWNLOADING;
// Then leave the synchronized block and continue.
} else if (mState == STATE_ERROR) {
return false;
} else if (mState == STATE_DOWNLOADED) {
return true;
} else /* if (mState == STATE_DOWNLOADING) */ {
try {
wait();
} catch (InterruptedException ex) {
// ignored.
}
continue;
}
}
// This is only reached for STATE_INIT->STATE_DOWNLOADING
openFileOrDownloadTempFile(jc);
}
}
private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
@Override
public BitmapRegionDecoder run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
jc, mFileDescriptor.getFileDescriptor(), false);
mWidth = decoder.getWidth();
mHeight = decoder.getHeight();
return decoder;
}
}
private class BitmapJob implements Job<Bitmap> {
private int mType;
protected BitmapJob(int type) {
mType = type;
}
@Override
public Bitmap run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
int targetSize = MediaItem.getTargetSize(mType);
Options options = new Options();
options.inPreferredConfig = Config.ARGB_8888;
Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
if (jc.isCancelled() || bitmap == null) {
return null;
}
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
} else {
bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
}
return bitmap;
}
}
@Override
public int getSupportedOperations() {
int supported = SUPPORT_EDIT | SUPPORT_SETAS;
if (isSharable()) supported |= SUPPORT_SHARE;
if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) {
supported |= SUPPORT_FULL_IMAGE;
}
if (usePanoramaViewer()) {
supported |= SUPPORT_PANORAMA;
}
return supported;
}
private boolean isSharable() {
// We cannot grant read permission to the receiver since we put
// the data URI in EXTRA_STREAM instead of the data part of an intent
// And there are issues in MediaUploader and Bluetooth file sender to
// share a general image data. So, we only share for local file.
return ContentResolver.SCHEME_FILE.equals(mUri.getScheme());
}
@Override
public int getMediaType() {
return MEDIA_TYPE_IMAGE;
}
@Override
public Uri getContentUri() {
return mUri;
}
@Override
public MediaDetails getDetails() {
MediaDetails details = super.getDetails();
if (mWidth != 0 && mHeight != 0) {
details.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
details.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
}
if (mContentType != null) {
details.addDetail(MediaDetails.INDEX_MIMETYPE, mContentType);
}
if (ContentResolver.SCHEME_FILE.equals(mUri.getScheme())) {
String filePath = mUri.getPath();
details.addDetail(MediaDetails.INDEX_PATH, filePath);
MediaDetails.extractExifInfo(details, filePath);
}
return details;
}
@Override
public String getMimeType() {
return mContentType;
}
@Override
protected void finalize() throws Throwable {
try {
if (mFileDescriptor != null) {
Utils.closeSilently(mFileDescriptor);
}
} finally {
super.finalize();
}
}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public int getRotation() {
return mRotation;
}
@Override
public boolean usePanoramaViewer() {
if (!mUsePanoramaViewerInitialized) {
Context context = mApplication.getAndroidContext();
mUsePanoramaViewer = LightCycleHelper.hasLightCycleView(context)
&& LightCycleHelper.isPanorama(mApplication.getContentResolver(),
getContentUri());
mUsePanoramaViewerInitialized = true;
}
return mUsePanoramaViewer;
}
}