summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBobby Georgescu <georgescu@google.com>2013-03-02 18:10:37 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-03-02 18:10:37 +0000
commit22fc7aac58ceceea9aaa391023cab8a02a8eb004 (patch)
tree1029e5124f71f4ed267bcad65fb99b5e14a6103a /src
parent56c35f8f9c173e618df34f94df6b4a0269bd7da5 (diff)
parent232aa1369beb0a26a35b7792b8a927e09023961f (diff)
downloadandroid_packages_apps_Snap-22fc7aac58ceceea9aaa391023cab8a02a8eb004.tar.gz
android_packages_apps_Snap-22fc7aac58ceceea9aaa391023cab8a02a8eb004.tar.bz2
android_packages_apps_Snap-22fc7aac58ceceea9aaa391023cab8a02a8eb004.zip
Merge "Refactor AutoThumbnailDrawable, fix race conditions" into gb-ub-photos-bryce
Diffstat (limited to 'src')
-rw-r--r--src/com/android/photos/PhotoSetFragment.java6
-rw-r--r--src/com/android/photos/drawables/AutoThumbnailDrawable.java141
-rw-r--r--src/com/android/photos/drawables/DataUriThumbnailDrawable.java54
-rw-r--r--src/com/android/photos/drawables/MtpThumbnailDrawable.java61
4 files changed, 208 insertions, 54 deletions
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
index 6c0e2e7c5..0e9efa4b1 100644
--- a/src/com/android/photos/PhotoSetFragment.java
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -33,7 +33,7 @@ import android.widget.ImageView;
import com.android.gallery3d.R;
import com.android.photos.data.PhotoSetLoader;
-import com.android.photos.drawables.AutoThumbnailDrawable;
+import com.android.photos.drawables.DataUriThumbnailDrawable;
import com.android.photos.views.GalleryThumbnailView;
import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
@@ -105,7 +105,7 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView iv = (ImageView) view;
- AutoThumbnailDrawable drawable = (AutoThumbnailDrawable) iv.getDrawable();
+ DataUriThumbnailDrawable drawable = (DataUriThumbnailDrawable) iv.getDrawable();
int width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
int height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
String path = cursor.getString(PhotoSetLoader.INDEX_DATA);
@@ -116,7 +116,7 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ImageView iv = new ImageView(context);
- AutoThumbnailDrawable drawable = new AutoThumbnailDrawable();
+ DataUriThumbnailDrawable drawable = new DataUriThumbnailDrawable();
iv.setImageDrawable(drawable);
int padding = (int) Math.ceil(2 * context.getResources().getDisplayMetrics().density);
iv.setPadding(padding, padding, padding, padding);
diff --git a/src/com/android/photos/drawables/AutoThumbnailDrawable.java b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
index 95283e16d..8959ecd82 100644
--- a/src/com/android/photos/drawables/AutoThumbnailDrawable.java
+++ b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
@@ -25,26 +25,25 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
-import android.text.TextUtils;
import android.util.Log;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
+import com.android.photos.data.GalleryBitmapPool;
+
+import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class AutoThumbnailDrawable extends Drawable {
+public abstract class AutoThumbnailDrawable<T> extends Drawable {
- private static final String TAG = "AutoMipMapDrawable";
+ private static final String TAG = "AutoThumbnailDrawable";
private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor();
+ private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance();
private static byte[] sTempStorage = new byte[64 * 1024];
// UI thread only
private Paint mPaint = new Paint();
private Matrix mDrawMatrix = new Matrix();
- private int mSampleSize = 1;
// Decoder thread only
private BitmapFactory.Options mOptions = new BitmapFactory.Options();
@@ -52,10 +51,16 @@ public class AutoThumbnailDrawable extends Drawable {
// Shared, guarded by mLock
private Object mLock = new Object();
private Bitmap mBitmap;
- private String mDataUri;
+ protected T mData;
private boolean mIsQueued;
private int mImageWidth, mImageHeight;
private Rect mBounds = new Rect();
+ private int mSampleSize = 1;
+ // mSampleSize is the target sample size for the full-size dimensions
+ // of the image (so if a preferred, smaller image is used, it might
+ // not reflect the sample size used when decoding that image). This
+ // value is used in refreshSampleSizeLocked to determine whether the
+ // image needs to be refreshed.
public AutoThumbnailDrawable() {
mPaint.setAntiAlias(true);
@@ -64,18 +69,35 @@ public class AutoThumbnailDrawable extends Drawable {
mOptions.inTempStorage = sTempStorage;
}
- public void setImage(String dataUri, int width, int height) {
- if (TextUtils.equals(mDataUri, dataUri)) return;
+ protected abstract byte[] getPreferredImageBytes(T data);
+ protected abstract InputStream getFallbackImageStream(T data);
+
+ // Must hold mLock when calling on different thread from setImage
+ protected abstract boolean dataChangedLocked(T data);
+
+ // Must only be called from one thread (usually the UI thread)
+ public void setImage(T data, int width, int height) {
+ if (!dataChangedLocked(data)) return;
synchronized (mLock) {
mImageWidth = width;
mImageHeight = height;
- mDataUri = dataUri;
- mBitmap = null;
+ mData = data;
+ setBitmapLocked(null);
refreshSampleSizeLocked();
}
invalidateSelf();
}
+ private void setBitmapLocked(Bitmap b) {
+ if (b == mBitmap) {
+ return;
+ }
+ if (mBitmap != null) {
+ sBitmapPool.put(mBitmap);
+ }
+ mBitmap = b;
+ }
+
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
@@ -148,7 +170,8 @@ public class AutoThumbnailDrawable extends Drawable {
} else {
scale = (float) dwidth / (float) vwidth;
}
- return (int) (scale + .5f);
+ int result = Math.round(scale);
+ return result > 0 ? result : 1;
}
private void refreshSampleSizeLocked() {
@@ -208,62 +231,78 @@ public class AutoThumbnailDrawable extends Drawable {
private final Runnable mLoadBitmap = new Runnable() {
@Override
public void run() {
- // TODO: Use bitmap pool
- String data;
- int sampleSize;
+ T data;
+ synchronized (mLock) {
+ data = mData;
+ }
+ int preferredSampleSize = 1;
+ byte[] preferred = getPreferredImageBytes(data);
+ boolean hasPreferred = (preferred != null && preferred.length > 0);
+ if (hasPreferred) {
+ mOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+ mOptions.inJustDecodeBounds = false;
+ }
+ int sampleSize, width, height;
synchronized (mLock) {
- data = mDataUri;
- sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight);
- mSampleSize = sampleSize;
+ if (dataChangedLocked(data)) {
+ return;
+ }
+ width = mImageWidth;
+ height = mImageHeight;
+ if (hasPreferred) {
+ preferredSampleSize = calculateSampleSizeLocked(
+ mOptions.outWidth, mOptions.outHeight);
+ }
+ sampleSize = calculateSampleSizeLocked(width, height);
mIsQueued = false;
}
- FileInputStream fis = null;
+ Bitmap b = null;
+ InputStream is = null;
try {
- ExifInterface exif = new ExifInterface(data);
- if (exif.hasThumbnail()) {
- byte[] thumbnail = exif.getThumbnail();
- mOptions.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(thumbnail, 0,
- thumbnail.length, mOptions);
- int exifThumbSampleSize = calculateSampleSizeLocked(
- mOptions.outWidth, mOptions.outHeight);
- mOptions.inJustDecodeBounds = false;
- mOptions.inSampleSize = exifThumbSampleSize;
- mBitmap = BitmapFactory.decodeByteArray(thumbnail, 0,
- thumbnail.length, mOptions);
- if (mBitmap != null) {
- synchronized (mLock) {
- if (TextUtils.equals(data, mDataUri)) {
- scheduleSelf(mUpdateBitmap, 0);
- }
- }
- return;
+ if (hasPreferred) {
+ mOptions.inSampleSize = preferredSampleSize;
+ mOptions.inBitmap = sBitmapPool.get(
+ mOptions.outWidth / preferredSampleSize,
+ mOptions.outHeight / preferredSampleSize);
+ b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+ if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+ sBitmapPool.put(mOptions.inBitmap);
+ mOptions.inBitmap = null;
+ }
+ }
+ if (b == null) {
+ is = getFallbackImageStream(data);
+ mOptions.inSampleSize = sampleSize;
+ mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize);
+ b = BitmapFactory.decodeStream(is, null, mOptions);
+ if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+ sBitmapPool.put(mOptions.inBitmap);
+ mOptions.inBitmap = null;
}
}
- fis = new FileInputStream(data);
- FileDescriptor fd = fis.getFD();
- mOptions.inSampleSize = sampleSize;
- mBitmap = BitmapFactory.decodeFileDescriptor(fd, null, mOptions);
} catch (Exception e) {
- Log.d("AsyncBitmap", "Failed to fetch bitmap", e);
+ Log.d(TAG, "Failed to fetch bitmap", e);
return;
} finally {
try {
- if (fis != null) {
- fis.close();
+ if (is != null) {
+ is.close();
}
} catch (Exception e) {}
- }
- synchronized (mLock) {
- if (TextUtils.equals(data, mDataUri)) {
- scheduleSelf(mUpdateBitmap, 0);
+ if (b != null) {
+ synchronized (mLock) {
+ if (!dataChangedLocked(data)) {
+ setBitmapLocked(b);
+ scheduleSelf(mUpdateBitmap, 0);
+ }
+ }
}
}
}
};
private final Runnable mUpdateBitmap = new Runnable() {
-
@Override
public void run() {
synchronized (AutoThumbnailDrawable.this) {
diff --git a/src/com/android/photos/drawables/DataUriThumbnailDrawable.java b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
new file mode 100644
index 000000000..c83b0c8fa
--- /dev/null
+++ b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 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.photos.drawables;
+
+import android.media.ExifInterface;
+import android.text.TextUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DataUriThumbnailDrawable extends AutoThumbnailDrawable<String> {
+
+ @Override
+ protected byte[] getPreferredImageBytes(String data) {
+ byte[] thumbnail = null;
+ try {
+ ExifInterface exif = new ExifInterface(data);
+ if (exif.hasThumbnail()) {
+ thumbnail = exif.getThumbnail();
+ }
+ } catch (IOException e) { }
+ return thumbnail;
+ }
+
+ @Override
+ protected InputStream getFallbackImageStream(String data) {
+ try {
+ return new FileInputStream(data);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean dataChangedLocked(String data) {
+ return !TextUtils.equals(mData, data);
+ }
+}
diff --git a/src/com/android/photos/drawables/MtpThumbnailDrawable.java b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
new file mode 100644
index 000000000..e35e06943
--- /dev/null
+++ b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.photos.drawables;
+
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+
+import com.android.gallery3d.ingest.MtpDeviceIndex;
+
+import java.io.InputStream;
+
+public class MtpThumbnailDrawable extends AutoThumbnailDrawable<MtpObjectInfo> {
+ public void setImage(MtpObjectInfo data) {
+ if (data == null) {
+ setImage(null, 0, 0);
+ } else {
+ setImage(data, data.getImagePixWidth(), data.getImagePixHeight());
+ }
+ }
+
+ @Override
+ protected byte[] getPreferredImageBytes(MtpObjectInfo data) {
+ if (data == null) {
+ return null;
+ }
+ MtpDevice device = MtpDeviceIndex.getInstance().getDevice();
+ if (device != null) {
+ return device.getThumbnail(data.getObjectHandle());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected InputStream getFallbackImageStream(MtpObjectInfo data) {
+ // No fallback
+ return null;
+ }
+
+ @Override
+ protected boolean dataChangedLocked(MtpObjectInfo data) {
+ // We only fetch the MtpObjectInfo once when creating
+ // the index so checking the reference is enough
+ return mData == data;
+ }
+
+}