Allow multiple image request in MediaItem.
Change-Id: I6f6cf1d7a5e807ba823b916ec24999523907801d
diff --git a/new3d/src/com/android/gallery3d/app/Gallery.java b/new3d/src/com/android/gallery3d/app/Gallery.java
index 3708609..8df1263 100644
--- a/new3d/src/com/android/gallery3d/app/Gallery.java
+++ b/new3d/src/com/android/gallery3d/app/Gallery.java
@@ -80,9 +80,6 @@
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
- synchronized (this) {
- if (mImageService != null) mImageService.close();
- }
}
@Override
diff --git a/new3d/src/com/android/gallery3d/data/DecodeService.java b/new3d/src/com/android/gallery3d/data/DecodeService.java
index d236af8..b067bac 100644
--- a/new3d/src/com/android/gallery3d/data/DecodeService.java
+++ b/new3d/src/com/android/gallery3d/data/DecodeService.java
@@ -19,12 +19,17 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
+import android.util.Log;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.FutureTask;
+import com.android.gallery3d.util.Utils;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
@@ -32,26 +37,22 @@
import java.util.concurrent.TimeUnit;
public class DecodeService {
+ private static final String TAG = "DecodeService";
+
private static final int CORE_POOL_SIZE = 1;
private static final int MAX_POOL_SIZE = 1;
private static final int KEEP_ALIVE_TIME = 10000; // 10 seconds
-
- private static DecodeService sInstance;
+ private static final int JPEG_MARK_POSITION = 60 * 1024;
private final Executor mExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- public static synchronized DecodeService getInstance() {
- if (sInstance == null) sInstance = new DecodeService();
- return sInstance;
- }
-
public Future<Bitmap> requestDecode(
File file, Options options, FutureListener<? super Bitmap> listener) {
if (options == null) options = new Options();
FutureTask<Bitmap> task = new DecodeFutureTask(
- new DecodeFile(file, options), listener);
+ new DecodeFile(file, options), options, listener);
mExecutor.execute(task);
return task;
}
@@ -71,22 +72,35 @@
offset, length, bytes.length));
}
FutureTask<Bitmap> task = new DecodeFutureTask(
- new DecodeByteArray(bytes, offset, length, options), listener);
+ new DecodeByteArray(bytes, offset, length, options),
+ options, listener);
+ mExecutor.execute(task);
+ return task;
+ }
+
+ public FutureTask<Bitmap> requestDecode(
+ File file, Options options, int targetLength, int maxPixelCount,
+ FutureListener<? super Bitmap> listener) {
+ if (options == null) options = new Options();
+ FutureTask<Bitmap> task = new DecodeFutureTask(
+ new DecodeAndSampleFile(file, options, targetLength, maxPixelCount),
+ options, listener);
mExecutor.execute(task);
return task;
}
private static class DecodeFutureTask extends FutureTask<Bitmap> {
- private Options mOptions;
+ private final Options mOptions;
- public DecodeFutureTask(
- Callable<Bitmap> callable, FutureListener<? super Bitmap> listener) {
+ public DecodeFutureTask(Callable<Bitmap> callable,
+ Options options, FutureListener<? super Bitmap> listener) {
super(callable, listener);
+ mOptions = options;
}
@Override
- public void onCancel() {
+ public void cancelTask() {
mOptions.requestCancelDecode();
}
}
@@ -125,4 +139,49 @@
}
}
+ private static class DecodeAndSampleFile implements Callable<Bitmap> {
+
+ private final int mTargetLength;
+ private final int mMaxPixelCount;
+ private final File mFile;
+ private final Options mOptions;
+
+ public DecodeAndSampleFile(
+ File file, Options options, int targetLength, int maxPixelCount) {
+ mFile = file;
+ mOptions = options;
+ mTargetLength = targetLength;
+ mMaxPixelCount = maxPixelCount;
+ }
+
+ public Bitmap call() throws IOException {
+ BufferedInputStream bis = new BufferedInputStream(
+ new FileInputStream(mFile), JPEG_MARK_POSITION);
+ try {
+ // Decode bufferedInput for calculating a sample size.
+ final BitmapFactory.Options options = mOptions;
+ options.inJustDecodeBounds = true;
+ bis.mark(JPEG_MARK_POSITION);
+ BitmapFactory.decodeStream(bis, null, options);
+ if (options.mCancel) return null;
+
+ try {
+ bis.reset();
+ } catch (IOException e) {
+ Log.w(TAG, "failed in resetting the buffer after reading the jpeg header", e);
+ bis.close();
+ bis = new BufferedInputStream(new FileInputStream(mFile));
+ }
+
+ options.inSampleSize = Utils.computeSampleSize(
+ options, mTargetLength, mMaxPixelCount);
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeStream(bis, null, options);
+ } finally {
+ bis.close();
+ }
+ }
+
+ }
+
}
diff --git a/new3d/src/com/android/gallery3d/data/DownloadService.java b/new3d/src/com/android/gallery3d/data/DownloadService.java
index 22bda53..47246e5 100644
--- a/new3d/src/com/android/gallery3d/data/DownloadService.java
+++ b/new3d/src/com/android/gallery3d/data/DownloadService.java
@@ -38,15 +38,10 @@
private static final int MAX_POOL_SIZE = 4;
private static final int KEEP_ALIVE_TIME = 10000;
- private static DownloadService sInstance;
-
private final ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- public DownloadService() {
- }
-
public FutureTask<Void> requestDownload(
URL url, File file, FutureListener<Void> listener) {
FutureTask<Void> task = new FutureTask<Void>(
diff --git a/new3d/src/com/android/gallery3d/data/ImageService.java b/new3d/src/com/android/gallery3d/data/ImageService.java
index 1d91047..33498ab 100644
--- a/new3d/src/com/android/gallery3d/data/ImageService.java
+++ b/new3d/src/com/android/gallery3d/data/ImageService.java
@@ -18,171 +18,214 @@
import android.content.ContentResolver;
import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
-import java.util.HashMap;
-import java.util.PriorityQueue;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureHelper;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.Utils;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
public class ImageService {
-
private static final String TAG = "ImageService";
- private static final int DECODE_TIMEOUT = 0;
+ private static final int MICRO_TARGET_PIXELS = 128 * 128;
+ private static final int CORE_POOL_SIZE = 1;
+ private static final int MAX_POOL_SIZE = 1;
+ private static final int KEEP_ALIVE_TIME = 10000; // 10 seconds
- private static final int INITIAL_TIMEOUT = 2000;
- private static final int MAXIMAL_TIMEOUT = 32000;
+ private final Executor mExecutor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
+ TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- private final HashMap<Integer, DecodeTask> mMap =
- new HashMap<Integer, DecodeTask>();
- private final PriorityQueue<DecodeTask> mQueue = new PriorityQueue<DecodeTask>();
- private final Handler mHandler;
private final ContentResolver mContentResolver;
- private boolean mActive = true;
- private int mTimeSerial;
-
- private DecodeTask mCurrentTask;
- private final DecodeThread mDecodeThread = new DecodeThread();
-
public ImageService(ContentResolver contentResolver) {
mContentResolver = contentResolver;
-
- mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message m) {
- if (m.what != DECODE_TIMEOUT) return;
- DecodeTask task = mCurrentTask;
- if (task != null) {
- task.mItem.cancelImageGeneration(mContentResolver, task.mType);
- }
- }
- };
-
- mDecodeThread.start();
}
- protected int requestImage(LocalMediaItem item, int type) {
- DecodeTask task = new DecodeTask();
- task.mRequestId = ++mTimeSerial;
- task.mItem = item;
- task.mType = type;
- task.mTimeout = INITIAL_TIMEOUT;
-
- synchronized (mQueue) {
- mMap.put(task.mRequestId, task);
- mQueue.add(task);
- if (mQueue.size() == 1) mQueue.notifyAll();
+ public Future<Bitmap> requestImageThumbnail(
+ int id, int type, FutureListener<? super Bitmap> listener) {
+ if (type != MediaItem.TYPE_MICROTHUMBNAIL
+ && type != MediaItem.TYPE_THUMBNAIL) {
+ throw new IllegalArgumentException(String.format("type = %s", type));
}
- return task.mRequestId;
+ GetImageThumbnail task =
+ new GetImageThumbnail(id, type, mContentResolver, listener);
+ mExecutor.execute(task);
+ return task;
}
- protected void cancelRequest(int requestId) {
- synchronized (mQueue) {
- DecodeTask task = mMap.remove(requestId);
- if (task == null) return;
- task.mCanceled = true;
- if (mQueue.remove(task)) {
- task.mItem.onImageCanceled(task.mType);
- } else {
- task.mItem.cancelImageGeneration(mContentResolver, task.mType);
- }
+ public Future<Bitmap> requestVideoThumbnail(
+ int id, int type, FutureListener<? super Bitmap> listener) {
+ if (type != MediaItem.TYPE_MICROTHUMBNAIL
+ && type != MediaItem.TYPE_THUMBNAIL
+ && type != MediaItem.TYPE_FULL_IMAGE) {
+ throw new IllegalArgumentException(String.format("type = %s", type));
}
+ GetVideoThumbnail task =
+ new GetVideoThumbnail(id, type, mContentResolver, listener);
+ mExecutor.execute(task);
+ return task;
}
- public void close() {
- synchronized (mQueue) {
- mActive = false;
- mQueue.notifyAll();
+ private static class GetImageThumbnail extends FutureHelper<Bitmap> implements Runnable {
+ private final int STATE_READY = 0;
+ private final int STATE_RUNNING = 1;
+ private final int STATE_CANCELED = 2;
+ private final int STATE_RAN = 4;
+
+ // mState tries to guard that onCancel() is called only when
+ // getThumbnail() is being executed.
+ private AtomicInteger mState = new AtomicInteger(STATE_READY);
+ private final int mId;
+ private final int mType;
+ private final ContentResolver mResolver;
+
+ public GetImageThumbnail(int id, int type, ContentResolver resolver,
+ FutureListener<? super Bitmap> listener) {
+ super(listener);
+ mId = id;
+ mType = type;
+ mResolver= resolver;
}
- }
- protected DecodeTask nextDecodeTask() {
- PriorityQueue<DecodeTask> queue = mQueue;
- synchronized (queue) {
- try {
- while (queue.isEmpty() && mActive) {
- queue.wait();
- }
- } catch (InterruptedException e) {
- Log.v(TAG, "decode-thread is interrupted");
- Thread.currentThread().interrupt();
- return null;
- }
- return !mActive ? null : queue.remove() ;
- }
- }
-
- private class DecodeThread extends Thread {
- @Override
public void run() {
- PriorityQueue<DecodeTask> queue = mQueue;
- ContentResolver resolver = mContentResolver;
+ Bitmap bitmap = null;
+ try {
+ bitmap = getThumbnail();
+ } catch (Throwable throwable) {
+ setException(throwable);
+ return;
+ }
+ if (bitmap == null && isCancelled()) {
+ cancelled();
+ } else {
+ setResult(bitmap);
+ }
+ }
- while (true) {
- DecodeTask task = nextDecodeTask();
- if (task == null) break;
- LocalMediaItem item = task.mItem;
- try {
- mCurrentTask = task;
- mHandler.sendEmptyMessageDelayed(
- DECODE_TIMEOUT, task.mTimeout);
- Bitmap bitmap = task.mCanceled
- ? null
- : item.generateImage(resolver, task.mType);
- mHandler.removeMessages(DECODE_TIMEOUT);
- mCurrentTask = null;
- if (bitmap != null) {
- task.mItem.onImageReady(task.mType, bitmap);
- synchronized (mQueue) {
- mMap.remove(task.mRequestId);
- }
- } else if (task.mCanceled) {
- task.mItem.onImageCanceled(task.mType);
- } else {
- // Try to decode the image again by increasing the
- // timeout by a factor of 2 unless MAXIMAL_TIMEOUT
- // is reached.
- task.mTimeout <<= 1;
- if (task.mTimeout > MAXIMAL_TIMEOUT) {
- throw new RuntimeException("decode timeout");
- } else {
- synchronized (mQueue) {
- mQueue.add(task);
- }
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "decode error", e);
- task.mItem.onImageError(task.mType, e);
- synchronized (mQueue) {
- mMap.remove(task.mRequestId);
- }
+ @Override
+ protected void onCancel() {
+ if (mState.compareAndSet(STATE_RUNNING, STATE_CANCELED)) {
+ switch (mType) {
+ case MediaItem.TYPE_THUMBNAIL:
+ case MediaItem.TYPE_MICROTHUMBNAIL:
+ // TODO: MediaProvider doesn't provide a way to specify
+ // which kind of thumbnail to be canceled. We should
+ // try to fix the issue in MediaProvider or here.
+ Images.Thumbnails.cancelThumbnailRequest(mResolver, mId);
+ break;
+ default:
+ throw new IllegalArgumentException();
}
}
- synchronized (mQueue) {
- for (DecodeTask task : mQueue) {
- task.mItem.onImageCanceled(task.mType);
+ }
+
+ private Bitmap getThumbnail() {
+ Bitmap result = null;
+
+ switch (mType) {
+ case MediaItem.TYPE_THUMBNAIL:
+ if (mState.compareAndSet(STATE_READY, STATE_RUNNING)) {
+ result = Images.Thumbnails.getThumbnail(
+ mResolver, mId, Images.Thumbnails.MINI_KIND, null);
+ mState.compareAndSet(STATE_RUNNING, STATE_RAN);
+ }
+ break;
+ case MediaItem.TYPE_MICROTHUMBNAIL: {
+ if (mState.compareAndSet(STATE_READY, STATE_RUNNING)) {
+ result = Images.Thumbnails.getThumbnail(
+ mResolver, mId, Images.Thumbnails.MINI_KIND, null);
+ mState.compareAndSet(STATE_RUNNING, STATE_RAN);
+ if (result != null) {
+ result = Utils.resize(result, MICRO_TARGET_PIXELS);
+ }
+ }
+ break;
}
- mQueue.clear();
- mMap.clear();
+ default:
+ throw new IllegalArgumentException();
}
+ return result;
}
}
- private static class DecodeTask implements Comparable<DecodeTask> {
- int mRequestId;
- int mTimeout;
- int mType;
- volatile boolean mCanceled;
- LocalMediaItem mItem;
+ private static class GetVideoThumbnail extends FutureHelper<Bitmap> implements Runnable {
+ private final int STATE_READY = 0;
+ private final int STATE_RUNNING = 1;
+ private final int STATE_CANCELED = 2;
+ private final int STATE_RAN = 4;
- public int compareTo(DecodeTask task) {
- return mTimeout != task.mTimeout
- ? mTimeout - task.mTimeout
- : mRequestId - task.mRequestId;
+ private AtomicInteger mState = new AtomicInteger(STATE_READY);
+ private final int mId;
+ private final int mType;
+ private final ContentResolver mResolver;
+
+ public GetVideoThumbnail(int id, int type, ContentResolver resolver,
+ FutureListener<? super Bitmap> listener) {
+ super(listener);
+ mId = id;
+ mType = type;
+ mResolver= resolver;
+ }
+
+ public void run() {
+ Bitmap bitmap = null;
+ try {
+ bitmap = getThumbnail();
+ } catch (Throwable throwable) {
+ setException(throwable);
+ return;
+ }
+ if (bitmap == null && isCancelled()) {
+ cancelled();
+ } else {
+ setResult(bitmap);
+ }
+ }
+
+ @Override
+ protected void onCancel() {
+ if (mState.compareAndSet(STATE_RUNNING, STATE_CANCELED)) {
+ // TODO: fix the issue that we cannot cancel only one request
+ Video.Thumbnails.cancelThumbnailRequest(mResolver, mId);
+ }
+ }
+
+ private Bitmap getThumbnail() {
+ Bitmap result = null;
+ switch (mType) {
+ case MediaItem.TYPE_FULL_IMAGE:
+ case MediaItem.TYPE_THUMBNAIL:
+ if (mState.compareAndSet(STATE_READY, STATE_RUNNING)) {
+ result = Images.Thumbnails.getThumbnail(
+ mResolver, mId, Images.Thumbnails.MINI_KIND, null);
+ mState.compareAndSet(STATE_RUNNING, STATE_RAN);
+ }
+ break;
+ case MediaItem.TYPE_MICROTHUMBNAIL: {
+ if (mState.compareAndSet(STATE_READY, STATE_RUNNING)) {
+ result = Video.Thumbnails.getThumbnail(
+ mResolver, mId, Video.Thumbnails.MINI_KIND, null);
+ mState.compareAndSet(STATE_RUNNING, STATE_RAN);
+ if (result != null) {
+ result = Utils.resize(result, MICRO_TARGET_PIXELS);
+ }
+ }
+ break;
+ }
+ default:
+ throw new IllegalArgumentException();
+ }
+ return result;
}
}
+
}
diff --git a/new3d/src/com/android/gallery3d/data/LocalAlbum.java b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
index ed76a92..f1de362 100644
--- a/new3d/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
@@ -103,7 +103,7 @@
try {
while (cursor.moveToNext()) {
if (mIsImage) {
- list.add(LocalImage.load(imageService, cursor, dataManager));
+ list.add(LocalImage.load(mContext, cursor, dataManager));
} else {
list.add(LocalVideo.load(imageService, cursor, dataManager));
}
diff --git a/new3d/src/com/android/gallery3d/data/LocalImage.java b/new3d/src/com/android/gallery3d/data/LocalImage.java
index 6aad1dc..b6751cf 100644
--- a/new3d/src/com/android/gallery3d/data/LocalImage.java
+++ b/new3d/src/com/android/gallery3d/data/LocalImage.java
@@ -16,25 +16,21 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.util.Utils;
-
-import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
-import android.util.Log;
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
+import com.android.gallery3d.app.GalleryContext;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+
+import java.io.File;
// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {
private static final int MICRO_TARGET_PIXELS = 128 * 128;
- private static final int JPEG_MARK_POSITION = 60 * 1024;
private static final int FULLIMAGE_TARGET_SIZE = 2048;
private static final int FULLIMAGE_MAX_NUM_PIXELS = 5 * 1024 * 1024;
@@ -69,91 +65,38 @@
private long mUniqueId;
private int mRotation;
+ private final GalleryContext mContext;
- protected LocalImage(ImageService imageService) {
- super(imageService);
+ protected LocalImage(GalleryContext context) {
+ mContext = context;
}
+ @Override
public long getUniqueId() {
return mUniqueId;
}
- protected Bitmap decodeImage(String path) throws IOException {
- // TODO: need to figure out why simply setting JPEG_MARK_POSITION doesn't work!
- BufferedInputStream bis = new BufferedInputStream(
- new FileInputStream(path), JPEG_MARK_POSITION);
- try {
- // Decode bufferedInput for calculating a sample size.
- final BitmapFactory.Options options = mOptions;
- options.inJustDecodeBounds = true;
- bis.mark(JPEG_MARK_POSITION);
- BitmapFactory.decodeStream(bis, null, options);
- if (options.mCancel) return null;
-
- try {
- bis.reset();
- } catch (IOException e) {
- Log.w(TAG, "failed in resetting the buffer after reading the jpeg header", e);
- bis.close();
- bis = new BufferedInputStream(new FileInputStream(path));
- }
-
- options.inSampleSize = Utils.computeSampleSize(options,
- FULLIMAGE_TARGET_SIZE, FULLIMAGE_MAX_NUM_PIXELS);
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeStream(bis, null, options);
- } finally {
- bis.close();
- }
- }
-
@Override
- protected void cancelImageGeneration(ContentResolver resolver, int type) {
- switch (type) {
- case TYPE_FULL_IMAGE:
- mOptions.requestCancelDecode();
- break;
- case TYPE_THUMBNAIL:
- case TYPE_MICROTHUMBNAIL:
- Images.Thumbnails.cancelThumbnailRequest(resolver, mId);
- break;
- default:
- throw new IllegalArgumentException();
+ public synchronized Future<Bitmap>
+ requestImage(int type, FutureListener<? super Bitmap> listener) {
+ if (type == TYPE_FULL_IMAGE) {
+ return mContext.getDecodeService().requestDecode(
+ new File(mFilePath), null, FULLIMAGE_TARGET_SIZE,
+ FULLIMAGE_MAX_NUM_PIXELS, listener);
+ } else {
+ return mContext.getImageService()
+ .requestImageThumbnail(mId, type, listener);
}
}
- @Override
- protected Bitmap generateImage(ContentResolver resolver, int type)
- throws Exception {
-
- switch (type) {
- case TYPE_FULL_IMAGE: {
- mOptions.mCancel = false;
- return decodeImage(mFilePath);
- }
- case TYPE_THUMBNAIL:
- return Images.Thumbnails.getThumbnail(
- resolver, mId, Images.Thumbnails.MINI_KIND, null);
- case TYPE_MICROTHUMBNAIL: {
- Bitmap bitmap = Images.Thumbnails.getThumbnail(
- resolver, mId, Images.Thumbnails.MINI_KIND, null);
- return bitmap == null
- ? null
- : Utils.resize(bitmap, MICRO_TARGET_PIXELS);
- }
- default:
- throw new IllegalArgumentException();
- }
- }
-
- public static LocalImage load(ImageService imageService, Cursor cursor,
+ public static LocalImage load(GalleryContext context, Cursor cursor,
DataManager dataManager) {
int itemId = cursor.getInt(INDEX_ID);
long uniqueId = DataManager.makeId(DataManager.ID_LOCAL_IMAGE, itemId);
LocalImage item = (LocalImage) dataManager.getFromCache(uniqueId);
if (item != null) return item;
- item = new LocalImage(imageService);
+ item = new LocalImage(context);
dataManager.putToCache(uniqueId, item);
item.mId = itemId;
diff --git a/new3d/src/com/android/gallery3d/data/LocalMediaItem.java b/new3d/src/com/android/gallery3d/data/LocalMediaItem.java
index 3c1a0ca..a3b8bd0 100644
--- a/new3d/src/com/android/gallery3d/data/LocalMediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/LocalMediaItem.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.data;
-import android.content.ContentResolver;
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.FutureHelper;
-import com.android.gallery3d.util.FutureListener;
-
//
// LocalMediaItem is an abstract class captures those common fields
// in LocalImage and LocalVideo.
@@ -41,69 +34,4 @@
protected long mDateAddedInSec;
protected long mDateModifiedInSec;
protected String mFilePath;
-
- protected int mRequestId[];
- private MyFuture mFutureBitmaps[];
-
- protected final ImageService mImageService;
-
- protected LocalMediaItem(ImageService imageService) {
- mImageService = imageService;
- mFutureBitmaps = new MyFuture[TYPE_COUNT];
- mRequestId = new int[TYPE_COUNT];
- }
-
- public synchronized Future<Bitmap>
- requestImage(int type, FutureListener<? super Bitmap> listener) {
- if (mFutureBitmaps[type] != null) {
- // TODO: we should not allow overlapped requests
- return null;
- } else {
- mFutureBitmaps[type] = new MyFuture(type, listener);
- mRequestId[type] = mImageService.requestImage(this, type);
- return mFutureBitmaps[type];
- }
- }
-
- private synchronized void cancelImageRequest(int type) {
- mImageService.cancelRequest(mRequestId[type]);
- mFutureBitmaps[type] = null;
- }
-
- protected synchronized void onImageReady(int type, Bitmap bitmap) {
- FutureHelper<Bitmap> helper = mFutureBitmaps[type];
- mFutureBitmaps[type] = null;
- if (helper != null) helper.setResult(bitmap);
- }
-
- protected synchronized void onImageError(int type, Throwable e) {
- FutureHelper<Bitmap> helper = mFutureBitmaps[type];
- mFutureBitmaps[type] = null;
- if (helper != null) helper.setException(e);
- }
-
- protected synchronized void onImageCanceled(int type) {
- FutureHelper<Bitmap> helper = mFutureBitmaps[type];
- if (helper != null) helper.cancelled();
- }
-
- abstract protected Bitmap generateImage(
- ContentResolver resolver, int type) throws Exception;
-
- abstract protected void cancelImageGeneration(
- ContentResolver resolver, int type);
-
- private class MyFuture extends FutureHelper<Bitmap> {
- private final int mSizeType;
-
- public MyFuture(int sizeType, FutureListener<? super Bitmap> listener) {
- super(listener);
- mSizeType = sizeType;
- }
-
- @Override
- public void onCancel() {
- cancelImageRequest(mSizeType);
- }
- }
}
diff --git a/new3d/src/com/android/gallery3d/data/LocalVideo.java b/new3d/src/com/android/gallery3d/data/LocalVideo.java
index 21328ff..5bb2fb7 100644
--- a/new3d/src/com/android/gallery3d/data/LocalVideo.java
+++ b/new3d/src/com/android/gallery3d/data/LocalVideo.java
@@ -16,14 +16,13 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.util.Utils;
-
-import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.VideoColumns;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+
// LocalVideo represents a video in the local storage.
public class LocalVideo extends LocalMediaItem {
@@ -56,38 +55,21 @@
private long mUniqueId;
public int mDurationInSec;
+ private final ImageService mImageService;
protected LocalVideo(ImageService imageService) {
- super(imageService);
+ mImageService = imageService;
}
+ @Override
public long getUniqueId() {
return mUniqueId;
}
@Override
- protected void cancelImageGeneration(ContentResolver resolver, int type) {
- Video.Thumbnails.cancelThumbnailRequest(resolver, mId);
- }
-
- @Override
- protected Bitmap generateImage(ContentResolver resolver, int type) {
- switch (type) {
- // Return a MINI_KIND bitmap in the cases of TYPE_FULL_IMAGE
- // and TYPE_THUMBNAIL.
- case TYPE_FULL_IMAGE:
- case TYPE_THUMBNAIL:
- return Video.Thumbnails.getThumbnail(
- resolver, mId, Video.Thumbnails.MINI_KIND, null);
- case TYPE_MICROTHUMBNAIL:
- Bitmap bitmap = Video.Thumbnails.getThumbnail(
- resolver, mId, Video.Thumbnails.MINI_KIND, null);
- return bitmap == null
- ? null
- : Utils.resize(bitmap, MICRO_TARGET_PIXELS);
- default:
- throw new IllegalArgumentException();
- }
+ public synchronized Future<Bitmap>
+ requestImage(int type, FutureListener<? super Bitmap> listener) {
+ return mImageService.requestVideoThumbnail(mId, type, listener);
}
public static LocalVideo load(ImageService imageService, Cursor cursor,
diff --git a/new3d/src/com/android/gallery3d/data/PicasaImage.java b/new3d/src/com/android/gallery3d/data/PicasaImage.java
index 92d599b..29f2cc8 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaImage.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaImage.java
@@ -31,13 +31,11 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
-import java.util.Arrays;
// PicasaImage is an image in the Picasa account.
public class PicasaImage extends MediaItem {
private static final String TAG = "PicasaImage";
- private final PicasaTask[] mTasks = new PicasaTask[MediaItem.TYPE_COUNT];
private final GalleryContext mContext;
private final PhotoEntry mData;
private final BlobCache mPicasaCache;
@@ -51,46 +49,42 @@
DataManager.ID_PICASA_IMAGE, (int) entry.id);
}
+ @Override
public long getUniqueId() {
return mUniqueId;
}
+ @Override
public synchronized Future<Bitmap>
requestImage(int type, FutureListener<? super Bitmap> listener) {
- if (mTasks[type] != null) {
- // TODO: enable the check when cancelling is done
- // throw new IllegalStateException();
- } else {
- URL photoUrl = getPhotoUrl(type);
- if (mPicasaCache != null) {
+ URL photoUrl = getPhotoUrl(type);
+ if (mPicasaCache != null) {
- // Try to get the image from cache.
- LookupRequest request = new LookupRequest();
- request.key = Utils.crc64Long(photoUrl.toString());
- boolean isCached = false;
- try {
- isCached = mPicasaCache.lookup(request);
- } catch (IOException e) {
- Log.w(TAG, "IOException in getting an image from " +
- "PicasaCache", e);
- }
-
- if (isCached) {
- byte[] uri = Utils.getBytesInUtf8(photoUrl.toString());
- if (isSameUri(uri, request.buffer)) {
- Log.i(TAG, "Get Image from Cache (type, url): " + type +
- " " + photoUrl.toString());
- DecodeService service = mContext.getDecodeService();
- return service.requestDecode(request.buffer, uri.length,
- request.length - uri.length, null, listener);
- }
- }
+ // Try to get the image from cache.
+ LookupRequest request = new LookupRequest();
+ request.key = Utils.crc64Long(photoUrl.toString());
+ boolean isCached = false;
+ try {
+ isCached = mPicasaCache.lookup(request);
+ } catch (IOException e) {
+ Log.w(TAG, "IOException in getting an image from " +
+ "PicasaCache", e);
}
- // Get the image from Picasaweb instead.
- mTasks[type] = new PicasaTask(type, photoUrl, listener);
+ if (isCached) {
+ byte[] uri = Utils.getBytesInUtf8(photoUrl.toString());
+ if (isSameUri(uri, request.buffer)) {
+ Log.i(TAG, "Get Image from Cache (type, url): " + type +
+ " " + photoUrl.toString());
+ DecodeService service = mContext.getDecodeService();
+ return service.requestDecode(request.buffer, uri.length,
+ request.length - uri.length, null, listener);
+ }
+ }
}
- return mTasks[type];
+
+ // Get the image from Picasaweb instead.
+ return new PicasaTask(type, photoUrl, listener);
}
private boolean isSameUri(byte[] uri, byte[] buffer) {
@@ -168,12 +162,6 @@
DecodeService service = mContext.getDecodeService();
return service.requestDecode(downloadedImage, null, this);
}
- case 2: {
- synchronized (PicasaImage.this) {
- mTasks[mType] = null;
- }
- break;
- }
}
return null;
}
diff --git a/new3d/src/com/android/gallery3d/ui/ScrollerHelper.java b/new3d/src/com/android/gallery3d/ui/ScrollerHelper.java
index 5a32cc3..c3c42a4 100644
--- a/new3d/src/com/android/gallery3d/ui/ScrollerHelper.java
+++ b/new3d/src/com/android/gallery3d/ui/ScrollerHelper.java
@@ -24,6 +24,7 @@
private static final int ANIM_KIND_FLING = 1;
private static final int ANIM_KIND_SCROLL = 2;
+
private static final int DECELERATED_FACTOR = 4;
private long mStartTime = NO_ANIMATION;
@@ -55,10 +56,11 @@
if (mAnimationKind == ANIM_KIND_SCROLL) {
f = 1 - f; // linear
} else if (mAnimationKind == ANIM_KIND_FLING) {
- f = 1 - f * f * f * f; // x ^ DECELERATED_FACTOR
+ f = 1 - (float) Math.pow(f, DECELERATED_FACTOR);
}
mPosition = Math.round(mStart + (mFinal - mStart) * f);
- Log.v("Fling", String.format("mStart = %s, mFinal = %s, mPosition = %s, f = %s, progress = %s",
+ Log.v("Fling", String.format(
+ "mStart = %s, mFinal = %s, mPosition = %s, f = %s, progress = %s",
mStart, mFinal, mPosition, f, progress));
if (mPosition == mFinal) {
mStartTime = NO_ANIMATION;
@@ -87,16 +89,18 @@
public void fling(float velocity, int min, int max) {
/*
- * The position formula: x = s + (e - s) * (1 - (1 - t / T) ^ d)
- * velocity formula: v = d * (e - s) * (1 - t / T) ^ (d - 1) / T
+ * The position formula: x(t) = s + (e - s) * (1 - (1 - t / T) ^ d)
+ * velocity formula: v(t) = d * (e - s) * (1 - t / T) ^ (d - 1) / T
* Thus,
* v0 = (e - s) / T * d => (e - s) = v0 * T / d
*/
mStartTime = START_ANIMATION;
mAnimationKind = ANIM_KIND_FLING;
mStart = mPosition;
- double x = Math.pow(Math.abs(velocity), 1.0 / (DECELERATED_FACTOR - 1));
- mDuration = (int) Math.round(FLING_DURATION_PARAM * x);
+
+ // Ta = T_ref * (Va / V_ref) ^ (1 / (d - 1)); V_ref = 1 pixel/second;
+ mDuration = (int) Math.round(FLING_DURATION_PARAM
+ * Math.pow(Math.abs(velocity), 1.0 / (DECELERATED_FACTOR - 1)));
int distance = Math.round(
velocity * mDuration / DECELERATED_FACTOR / 1000);
mFinal = Utils.clamp(mStart + distance, min, max);
diff --git a/new3d/src/com/android/gallery3d/util/FutureListener.java b/new3d/src/com/android/gallery3d/util/FutureListener.java
index 147030b..0770660 100644
--- a/new3d/src/com/android/gallery3d/util/FutureListener.java
+++ b/new3d/src/com/android/gallery3d/util/FutureListener.java
@@ -1,5 +1,5 @@
package com.android.gallery3d.util;
public interface FutureListener<V> {
- public void onFutureDone(Future<? extends V> future);
+ public void onFutureDone(Future<? extends V> future);
}
diff --git a/new3d/src/com/android/gallery3d/util/FutureTask.java b/new3d/src/com/android/gallery3d/util/FutureTask.java
index 0f4ff75..ca2c68c 100644
--- a/new3d/src/com/android/gallery3d/util/FutureTask.java
+++ b/new3d/src/com/android/gallery3d/util/FutureTask.java
@@ -17,99 +17,120 @@
*/
public class FutureTask<V> implements Future<V>, Runnable {
- private static final int STATE_READY = 0;
- private static final int STATE_RUNNING = 1;
- private static final int STATE_CANCELLED = 2;
- private static final int STATE_INTERRUPTED = 4;
- private static final int STATE_RAN = 8;
+ private static final int STATE_READY = 0;
+ private static final int STATE_RUNNING = 1;
+ private static final int STATE_CANCELLED = 2;
+ private static final int STATE_INTERRUPTED = 4;
+ private static final int STATE_RAN = 8;
- private Callable<V> mCallable;
- private final MyHelper mHelper;
- private volatile Thread mRunner;
- private AtomicInteger mState = new AtomicInteger(STATE_READY);
+ private Callable<V> mCallable;
+ private final MyHelper mHelper;
+ private volatile Thread mRunner;
+ private AtomicInteger mState = new AtomicInteger(STATE_READY);
+ private final boolean mInterruptible;
- public FutureTask(Callable<V> callable, FutureListener<? super V> listener) {
- mCallable = callable;
- mHelper = new MyHelper(listener);
- }
+ public FutureTask(Callable<V> callable, FutureListener<? super V> listener) {
+ this(callable, false, listener);
+ }
- public void requestCancel() {
- mHelper.requestCancel();
- }
+ // @param interruptible Sets whether this task is interruptible, if true,
+ // the thread running the task will be interrupted when canceling.
+ public FutureTask(Callable<V> callable,
+ boolean interruptible, FutureListener<? super V> listener) {
+ mInterruptible = interruptible;
+ mCallable = callable;
+ mHelper = new MyHelper(listener);
+ }
- public V get() throws ExecutionException, InterruptedException {
- return mHelper.get();
- }
+ public void requestCancel() {
+ mHelper.requestCancel();
+ }
- public V get(long duration, TimeUnit unit) throws ExecutionException,
- InterruptedException, TimeoutException {
- return mHelper.get(duration, unit);
- }
+ public V get() throws ExecutionException, InterruptedException {
+ return mHelper.get();
+ }
- public boolean isCancelled() {
- return mHelper.isCancelled();
- }
+ public V get(long duration, TimeUnit unit) throws ExecutionException,
+ InterruptedException, TimeoutException {
+ return mHelper.get(duration, unit);
+ }
- public boolean isDone() {
- return mHelper.isDone();
- }
+ public boolean isCancelled() {
+ return mHelper.isCancelled();
+ }
- public void run() {
- mRunner = Thread.currentThread();
- if (!mState.compareAndSet(STATE_READY, STATE_RUNNING)) return;
+ public boolean isDone() {
+ return mHelper.isDone();
+ }
- try {
- V result = mCallable.call();
- mCallable = null;
- if (result == null && mHelper.isCancelling()) {
- mHelper.cancelled();
- } else {
- mHelper.setResult(result);
- }
- } catch (InterruptedException e) {
- if (mHelper.isCancelling()) {
- mHelper.cancelled();
- } else {
- mHelper.setException(e);
- }
- } catch (InterruptedIOException e) {
- if (mHelper.isCancelling()) {
- mHelper.cancelled();
- } else {
- mHelper.setException(e);
- }
- } catch (Throwable t) {
- mHelper.setException(t);
- }
+ public void run() {
+ mRunner = Thread.currentThread();
+ if (!mState.compareAndSet(STATE_READY, STATE_RUNNING)) return;
- if (!mState.compareAndSet(STATE_RUNNING, STATE_RAN)) {
- // STATE_INTERRUPTED
- synchronized (this) {
- Thread.interrupted(); // consume the interrupted signal
- }
- }
- mRunner = null;
- }
-
- protected synchronized void onCancel() {
- if (mState.compareAndSet(STATE_RUNNING, STATE_INTERRUPTED)){
- mRunner.interrupt();
+ boolean noException = false;
+ V result = null;
+ try {
+ result = mCallable.call();
+ noException = true;
+ } catch (InterruptedException e) {
+ if (mHelper.isCancelling()) {
+ mHelper.cancelled();
+ } else {
+ mHelper.setException(e);
+ }
+ } catch (InterruptedIOException e) {
+ if (mHelper.isCancelling()) {
+ mHelper.cancelled();
+ } else {
+ mHelper.setException(e);
+ }
+ } catch (Throwable t) {
+ mHelper.setException(t);
+ } finally {
+ mCallable = null;
}
- }
- private class MyHelper extends FutureHelper<V> {
+ if (noException) {
+ if (result == null && mHelper.isCancelling()) {
+ mHelper.cancelled();
+ } else {
+ mHelper.setResult(result);
+ }
+ }
- MyHelper(FutureListener<? super V> listener) {
- super(listener);
- }
+ if (mInterruptible &&
+ !mState.compareAndSet(STATE_RUNNING, STATE_RAN)) {
+ // STATE_INTERRUPTED
+ synchronized (this) {
+ Thread.interrupted(); // consume the interrupted signal
+ }
+ }
+ mRunner = null;
+ }
- @Override
+ protected void onRequestCancel() {
+ }
+
+ protected synchronized void cancelTask() {
+ if (mState.compareAndSet(STATE_RUNNING, STATE_INTERRUPTED)){
+ if (mInterruptible) mRunner.interrupt();
+ onRequestCancel();
+ }
+ }
+
+ private class MyHelper extends FutureHelper<V> {
+
+ MyHelper(FutureListener<? super V> listener) {
+ super(listener);
+ }
+
+ @Override
protected void onCancel() {
- if (mState.compareAndSet(STATE_READY, STATE_CANCELLED)) {
- cancelled();
- } else if (mState.get() == STATE_RUNNING) {
- FutureTask.this.onCancel();
- } // else mState == STATE_DONE;
- }
- }
+ if (mState.compareAndSet(STATE_READY, STATE_CANCELLED)) {
+ cancelled();
+ } else if (mState.get() == STATE_RUNNING) {
+ FutureTask.this.cancelTask();
+ } // else mState == STATE_DONE;
+ }
+ }
}