diff options
Diffstat (limited to 'src/com/android/camera/gallery/BaseImage.java')
-rw-r--r-- | src/com/android/camera/gallery/BaseImage.java | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/src/com/android/camera/gallery/BaseImage.java b/src/com/android/camera/gallery/BaseImage.java new file mode 100644 index 0000000..a43064c --- /dev/null +++ b/src/com/android/camera/gallery/BaseImage.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2009 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.camera.gallery; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore.Images; +import android.util.Log; + + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; + +/** + * Represents a particular image and provides access to the underlying bitmap + * and two thumbnail bitmaps as well as other information such as the id, and + * the path to the actual image data. + */ +public abstract class BaseImage implements IImage { + + private static final boolean VERBOSE = false; + private static final String TAG = "BaseImage"; + + static final int BYTES_PER_MINTHUMB = 10000; + private static final byte [] sMiniThumbData = new byte[BYTES_PER_MINTHUMB]; + + protected ContentResolver mContentResolver; + protected long mId, mMiniThumbMagic; + protected BaseImageList mContainer; + protected HashMap<String, String> mExifData; + protected int mCursorRow; + + protected BaseImage(long id, long miniThumbId, ContentResolver cr, + BaseImageList container, int cursorRow) { + mContentResolver = cr; + mId = id; + mMiniThumbMagic = miniThumbId; + mContainer = container; + mCursorRow = cursorRow; + } + + protected abstract Bitmap.CompressFormat compressionType(); + + public void commitChanges() { + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + c.commitUpdates(); + c.requery(); + } + } + } + + private class CompressImageToFile extends BaseCancelable + implements IGetBooleanCancelable { + private ThreadSafeOutputStream mOutputStream = null; + + private Bitmap mBitmap; + private Uri mUri; + private byte[] mJpegData; + + public CompressImageToFile(Bitmap bitmap, byte[] jpegData, Uri uri) { + mBitmap = bitmap; + mUri = uri; + mJpegData = jpegData; + } + + @Override + public boolean doCancelWork() { + if (mOutputStream != null) { + mOutputStream.close(); + return true; + } + return false; + } + + public boolean get() { + try { + long t1 = System.currentTimeMillis(); + OutputStream delegate = mContentResolver.openOutputStream(mUri); + synchronized (this) { + checkCanceled(); + mOutputStream = new ThreadSafeOutputStream(delegate); + } + long t2 = System.currentTimeMillis(); + if (mBitmap != null) { + mBitmap.compress(compressionType(), 75, mOutputStream); + } else { + long x1 = System.currentTimeMillis(); + mOutputStream.write(mJpegData); + long x2 = System.currentTimeMillis(); + if (VERBOSE) { + Log.v(TAG, "done writing... " + mJpegData.length + + " bytes took " + (x2 - x1)); + } + } + long t3 = System.currentTimeMillis(); + if (VERBOSE) { + Log.v(TAG, String.format( + "CompressImageToFile.get took %d (%d, %d)", + (t3 - t1), (t2 - t1), (t3 - t2))); + } + return true; + } catch (FileNotFoundException ex) { + return false; + } catch (CanceledException ex) { + return false; + } catch (IOException ex) { + return false; + } finally { + Util.closeSiliently(mOutputStream); + acknowledgeCancel(); + } + } + } + + /** + * Take a given bitmap and compress it to a file as described + * by the Uri parameter. + * + * @param bitmap the bitmap to be compressed/stored + * @param uri where to store the bitmap + * @return true if we succeeded + */ + protected IGetBooleanCancelable compressImageToFile( + Bitmap bitmap, byte [] jpegData, Uri uri) { + return new CompressImageToFile(bitmap, jpegData, uri); + } + + @Override + public boolean equals(Object other) { + if (other == null || !(other instanceof Image)) return false; + return fullSizeImageUri().equals(((Image) other).fullSizeImageUri()); + } + + @Override + public int hashCode() { + return fullSizeImageUri().toString().hashCode(); + } + + public Bitmap fullSizeBitmap(int targetWidthHeight) { + return fullSizeBitmap(targetWidthHeight, true); + } + + protected Bitmap fullSizeBitmap( + int targetWidthHeight, boolean rotateAsNeeded) { + Uri url = mContainer.contentUri(mId); + if (VERBOSE) Log.v(TAG, "getCreateBitmap for " + url); + if (url == null) return null; + + Bitmap b = makeBitmap(targetWidthHeight, url); + if (b != null && rotateAsNeeded) { + b = Util.rotate(b, getDegreesRotated()); + } + return b; + } + + private class LoadBitmapCancelable extends BaseCancelable + implements IGetBitmapCancelable { + private ParcelFileDescriptor mPFD; + private BitmapFactory.Options mOptions = new BitmapFactory.Options(); + private long mCancelInitiationTime; + private int mTargetWidthHeight; + + public LoadBitmapCancelable( + ParcelFileDescriptor pfdInput, int targetWidthHeight) { + mPFD = pfdInput; + mTargetWidthHeight = targetWidthHeight; + } + + @Override + public boolean doCancelWork() { + if (VERBOSE) Log.v(TAG, "requesting bitmap load cancel"); + mCancelInitiationTime = System.currentTimeMillis(); + mOptions.requestCancelDecode(); + return true; + } + + public Bitmap get() { + try { + Bitmap b = makeBitmap( + mTargetWidthHeight, fullSizeImageUri(), mPFD, mOptions); + if (mCancelInitiationTime != 0 && VERBOSE) { + Log.v(TAG, "cancelation of bitmap load success==" + + (b == null ? "TRUE" : "FALSE") + " -- took " + + (System.currentTimeMillis() + - mCancelInitiationTime)); + } + if (b != null) { + b = Util.rotate(b, getDegreesRotated()); + } + return b; + } catch (RuntimeException ex) { + return null; + } catch (Error e) { + return null; + } finally { + acknowledgeCancel(); + } + } + } + + + public IGetBitmapCancelable fullSizeBitmapCancelable( + int targetWidthHeight) { + try { + ParcelFileDescriptor pfdInput = mContentResolver + .openFileDescriptor(fullSizeImageUri(), "r"); + return new LoadBitmapCancelable(pfdInput, targetWidthHeight); + } catch (FileNotFoundException ex) { + return null; + } catch (UnsupportedOperationException ex) { + return null; + } + } + + public InputStream fullSizeImageData() { + try { + InputStream input = mContentResolver.openInputStream( + fullSizeImageUri()); + return input; + } catch (IOException ex) { + return null; + } + } + + public long fullSizeImageId() { + return mId; + } + + public Uri fullSizeImageUri() { + return mContainer.contentUri(mId); + } + + public IImageList getContainer() { + return mContainer; + } + + Cursor getCursor() { + return mContainer.getCursor(); + } + + public long getDateTaken() { + if (mContainer.indexDateTaken() < 0) return 0; + Cursor c = getCursor(); + synchronized (c) { + c.moveToPosition(getRow()); + return c.getLong(mContainer.indexDateTaken()); + } + } + + protected int getDegreesRotated() { + return 0; + } + + public String getMimeType() { + if (mContainer.indexMimeType() < 0) { + Cursor c = mContentResolver.query(fullSizeImageUri(), + new String[] { "_id", Images.Media.MIME_TYPE }, + null, null, null); + try { + return c.moveToFirst() ? c.getString(1) : ""; + } finally { + c.close(); + } + } else { + String mimeType = null; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + mimeType = c.getString(mContainer.indexMimeType()); + } + } + return mimeType; + } + } + + public String getDescription() { + if (mContainer.indexDescription() < 0) { + Cursor c = mContentResolver.query(fullSizeImageUri(), + new String[] { "_id", Images.Media.DESCRIPTION }, + null, null, null); + try { + return c.moveToFirst() ? c.getString(1) : ""; + } finally { + c.close(); + } + } else { + String description = null; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + description = c.getString(mContainer.indexDescription()); + } + } + return description; + } + } + + public boolean getIsPrivate() { + if (mContainer.indexPrivate() < 0) return false; + boolean isPrivate = false; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + isPrivate = c.getInt(mContainer.indexPrivate()) != 0; + } + } + return isPrivate; + } + + public double getLatitude() { + if (mContainer.indexLatitude() < 0) return 0D; + Cursor c = getCursor(); + synchronized (c) { + c.moveToPosition(getRow()); + return c.getDouble(mContainer.indexLatitude()); + } + } + + public double getLongitude() { + if (mContainer.indexLongitude() < 0) return 0D; + Cursor c = getCursor(); + synchronized (c) { + c.moveToPosition(getRow()); + return c.getDouble(mContainer.indexLongitude()); + } + } + + public String getTitle() { + String name = null; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + if (mContainer.indexTitle() != -1) { + name = c.getString(mContainer.indexTitle()); + } + } + } + return name != null && name.length() > 0 ? name : String.valueOf(mId); + } + + public String getDisplayName() { + if (mContainer.indexDisplayName() < 0) { + Cursor c = mContentResolver.query(fullSizeImageUri(), + new String[] { "_id", Images.Media.DISPLAY_NAME }, + null, null, null); + try { + if (c.moveToFirst()) return c.getString(1); + } finally { + c.close(); + } + } else { + String name = null; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + name = c.getString(mContainer.indexDisplayName()); + } + } + if (name != null && name.length() > 0) { + return name; + } + } + return String.valueOf(mId); + } + + public String getPicasaId() { + return null; + } + + public int getRow() { + return mCursorRow; + } + + public int getWidth() { + ParcelFileDescriptor input = null; + try { + Uri uri = fullSizeImageUri(); + input = mContentResolver.openFileDescriptor(uri, "r"); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor( + input.getFileDescriptor(), null, options); + return options.outWidth; + } catch (IOException ex) { + return 0; + } finally { + Util.closeSiliently(input); + } + } + + public int getHeight() { + ParcelFileDescriptor input = null; + try { + Uri uri = fullSizeImageUri(); + input = mContentResolver.openFileDescriptor(uri, "r"); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor( + input.getFileDescriptor(), null, options); + return options.outHeight; + } catch (IOException ex) { + return 0; + } finally { + Util.closeSiliently(input); + } + } + + public boolean hasLatLong() { + if (mContainer.indexLatitude() < 0 || mContainer.indexLongitude() < 0) { + return false; + } + Cursor c = getCursor(); + synchronized (c) { + c.moveToPosition(getRow()); + return !c.isNull(mContainer.indexLatitude()) + && !c.isNull(mContainer.indexLongitude()); + } + } + + public long imageId() { + return mId; + } + + /** + * Make a bitmap from a given Uri. + * + * @param uri + */ + private Bitmap makeBitmap(int targetWidthOrHeight, Uri uri) { + ParcelFileDescriptor input = null; + try { + input = mContentResolver.openFileDescriptor(uri, "r"); + return makeBitmap(targetWidthOrHeight, uri, input, null); + } catch (IOException ex) { + return null; + } finally { + Util.closeSiliently(input); + } + } + + protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, + ParcelFileDescriptor pfdInput, BitmapFactory.Options options) { + return mContainer.makeBitmap(targetWidthHeight, uri, pfdInput, options); + } + + public Bitmap miniThumbBitmap() { + try { + long id = mId; + long dbMagic = mMiniThumbMagic; + if (dbMagic == 0 || dbMagic == id) { + dbMagic = ((BaseImageList) getContainer()) + .checkThumbnail(this, getCursor(), getRow()); + if (VERBOSE) { + Log.v(TAG, "after computing thumbnail dbMagic is " + + dbMagic); + } + } + + synchronized (sMiniThumbData) { + dbMagic = mMiniThumbMagic; + byte [] data = mContainer.getMiniThumbFromFile(id, + sMiniThumbData, dbMagic); + if (data == null) { + byte[][] createdThumbData = new byte[1][]; + try { + dbMagic = ((BaseImageList) getContainer()) + .checkThumbnail(this, getCursor(), getRow(), + createdThumbData); + } catch (IOException ex) { + // Typically IOException because the sd card is full. + // But createdThumbData may have been filled in, so + // continue on. + } + data = createdThumbData[0]; + } + if (data == null) { + data = mContainer.getMiniThumbFromFile(id, sMiniThumbData, + dbMagic); + } + if (data == null) { + if (VERBOSE) { + Log.v(TAG, "unable to get miniThumbBitmap," + + " data is null"); + } + } + if (data != null) { + Bitmap b = BitmapFactory.decodeByteArray(data, 0, + data.length); + if (b == null && VERBOSE) { + Log.v(TAG, "couldn't decode byte array, " + + "length was " + data.length); + } + return b; + } + } + return null; + } catch (Throwable ex) { + if (VERBOSE) { + Log.e(TAG, "miniThumbBitmap got exception " + ex.toString()); + for (StackTraceElement s : ex.getStackTrace()) + Log.e(TAG, "... " + s.toString()); + } + return null; + } + } + + public void onRemove() { + mContainer.mCache.remove(mId); + } + + protected void saveMiniThumb(Bitmap source) throws IOException { + mContainer.saveMiniThumbToFile(source, fullSizeImageId(), 0); + } + + public void setDescription(String description) { + if (mContainer.indexDescription() < 0) return; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + c.updateString(mContainer.indexDescription(), description); + } + } + } + + public void setIsPrivate(boolean isPrivate) { + if (mContainer.indexPrivate() < 0) return; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + c.updateInt(mContainer.indexPrivate(), isPrivate ? 1 : 0); + } + } + } + + public void setName(String name) { + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + c.updateString(mContainer.indexTitle(), name); + } + } + } + + public void setPicasaId(String id) { + Cursor c = null; + try { + c = mContentResolver.query(fullSizeImageUri(), + new String[] { "_id", Images.Media.PICASA_ID }, + null, null, null); + if (c != null && c.moveToFirst()) { + if (VERBOSE) { + Log.v(TAG, "storing picasaid " + id + " for " + + fullSizeImageUri()); + } + c.updateString(1, id); + c.commitUpdates(); + if (VERBOSE) { + Log.v(TAG, "updated image with picasa id " + id); + } + } + } finally { + if (c != null) + c.close(); + } + } + + public Uri thumbUri() { + Uri uri = fullSizeImageUri(); + // The value for the query parameter cannot be null :-(, + // so using a dummy "1" + uri = uri.buildUpon().appendQueryParameter("thumb", "1").build(); + return uri; + } + + @Override + public String toString() { + return fullSizeImageUri().toString(); + } +} |