/* * 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.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.support.v4.content.CursorLoader; import android.widget.Toast; import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.app.PanoramaMetadataSupport; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; 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 PanoramaMetadataSupport mPanoramaMetadata = new PanoramaMetadataSupport(this); 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 (getPathFromURI(mUri) .toLowerCase().contains("mpo") || getPathFromURI(mUri) .toLowerCase().contains("jps")) { Toast.makeText(mApplication.getAndroidContext(), "3D", Toast.LENGTH_LONG).show(); bitmap = drawTextToBitmap(mApplication.getAndroidContext(), "3D", bitmap); } Toast.makeText(mApplication.getAndroidContext(), getPathFromURI(mUri), Toast.LENGTH_LONG).show(); if (mType == MediaItem.TYPE_MICROTHUMBNAIL) { bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true); } else { bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true); } return bitmap; } } public Bitmap drawTextToBitmap(Context gContext, String gText, Bitmap bitmap) { Resources resources = gContext.getResources(); float scale = resources.getDisplayMetrics().density; android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig(); // set default bitmap config if none if(bitmapConfig == null) { bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; } // resource bitmaps are imutable, // so we need to convert it to mutable one bitmap = bitmap.copy(bitmapConfig, true); Canvas canvas = new Canvas(bitmap); // new antialised Paint Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // text color - #3D3D3D paint.setColor(Color.rgb(61, 61, 61)); // text size in pixels paint.setTextSize((int) (25 * scale)); // text shadow paint.setShadowLayer(1f, 0f, 1f, Color.WHITE); // draw text to the Canvas center Rect bounds = new Rect(); paint.setTextAlign(Align.CENTER); paint.getTextBounds(gText, 0, gText.length(), bounds); int x = (bitmap.getWidth() - bounds.width())/2; int y = (bitmap.getHeight() + bounds.height())/2; canvas.drawText(gText, x * scale, y * scale, paint); return bitmap; } private String getPathFromURI(Uri contentUri) { String[] proj = { MediaStore.Images.Media.DATA }; CursorLoader loader = new CursorLoader(mApplication.getAndroidContext(), contentUri, proj, null, null, null); Cursor cursor = loader.loadInBackground(); int column_index = cursor .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(column_index); } @Override public int getSupportedOperations() { int supported = SUPPORT_EDIT | SUPPORT_SETAS; if (isSharable()) supported |= SUPPORT_SHARE; if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) { supported |= SUPPORT_FULL_IMAGE; } return supported; } @Override public void getPanoramaSupport(PanoramaSupportCallback callback) { mPanoramaMetadata.getPanoramaSupport(mApplication, callback); } @Override public void clearCachedPanoramaSupport() { mPanoramaMetadata.clearCachedValues(); } 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; } }