summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOwen Lin <owenlin@google.com>2009-04-20 15:59:57 +0800
committerOwen Lin <owenlin@google.com>2009-04-29 22:45:36 -0700
commit41a8578529dedf1e81218d4429954d6ccd9d67af (patch)
treeb78fc2d6b02fc5c06aae5e1eab8773867905eb80
parent208ec6d4e4211504975404e6af22d6be7c03085d (diff)
downloadLegacyCamera-41a8578529dedf1e81218d4429954d6ccd9d67af.zip
LegacyCamera-41a8578529dedf1e81218d4429954d6ccd9d67af.tar.gz
LegacyCamera-41a8578529dedf1e81218d4429954d6ccd9d67af.tar.bz2
Improve the design of ICancelable.
-rw-r--r--src/com/android/camera/Camera.java4
-rw-r--r--src/com/android/camera/CropImage.java4
-rwxr-xr-x[-rw-r--r--]src/com/android/camera/ImageManager.java62
-rw-r--r--src/com/android/camera/PriorityTask.java13
-rw-r--r--src/com/android/camera/ViewImage.java39
-rw-r--r--src/com/android/camera/gallery/BaseCancelable.java177
-rw-r--r--src/com/android/camera/gallery/BaseImage.java33
-rw-r--r--src/com/android/camera/gallery/Cancelable.java56
-rw-r--r--src/com/android/camera/gallery/CanceledException.java23
-rw-r--r--src/com/android/camera/gallery/ICancelable.java32
-rw-r--r--src/com/android/camera/gallery/IImage.java2
-rw-r--r--src/com/android/camera/gallery/Image.java106
-rw-r--r--src/com/android/camera/gallery/UriImage.java16
-rw-r--r--src/com/android/camera/gallery/VideoObject.java2
-rw-r--r--tests/src/com/android/camera/gallery/BaseCancelableTest.java232
15 files changed, 561 insertions, 240 deletions
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
index 2167b2a..fe1af38 100644
--- a/src/com/android/camera/Camera.java
+++ b/src/com/android/camera/Camera.java
@@ -16,7 +16,7 @@
package com.android.camera;
-import com.android.camera.gallery.ICancelable;
+import com.android.camera.gallery.Cancelable;
import com.android.camera.gallery.IImage;
import com.android.camera.gallery.IImageList;
@@ -458,7 +458,7 @@ public class Camera extends Activity implements View.OnClickListener,
private boolean mCapturing = false;
private Uri mLastContentUri;
- private ICancelable<Void> mAddImageCancelable;
+ private Cancelable<Void> mAddImageCancelable;
Bitmap mCaptureOnlyBitmap;
diff --git a/src/com/android/camera/CropImage.java b/src/com/android/camera/CropImage.java
index 99817ac..a64b02c 100644
--- a/src/com/android/camera/CropImage.java
+++ b/src/com/android/camera/CropImage.java
@@ -16,7 +16,7 @@
package com.android.camera;
-import com.android.camera.gallery.ICancelable;
+import com.android.camera.gallery.Cancelable;
import com.android.camera.gallery.IImage;
import com.android.camera.gallery.IImageList;
@@ -374,7 +374,7 @@ public class CropImage extends Activity {
directory.toString(),
fileName + "-" + x + ".jpg");
- ICancelable<Void> cancelable =
+ Cancelable<Void> cancelable =
ImageManager.storeImage(
newUri,
getContentResolver(),
diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java
index cbf025e..e936824 100644..100755
--- a/src/com/android/camera/ImageManager.java
+++ b/src/com/android/camera/ImageManager.java
@@ -18,9 +18,8 @@ package com.android.camera;
import com.android.camera.gallery.BaseCancelable;
import com.android.camera.gallery.BaseImageList;
-import com.android.camera.gallery.CanceledException;
+import com.android.camera.gallery.Cancelable;
import com.android.camera.gallery.DrmImageList;
-import com.android.camera.gallery.ICancelable;
import com.android.camera.gallery.IImage;
import com.android.camera.gallery.IImageList;
import com.android.camera.gallery.Image;
@@ -47,6 +46,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.concurrent.ExecutionException;
/**
* ImageManager is used to retrieve and store images
@@ -54,7 +54,6 @@ import java.util.HashMap;
*/
public class ImageManager {
private static final String TAG = "ImageManager";
- private static ImageManager sInstance = null;
private static final Uri STORAGE_URI = Images.Media.EXTERNAL_CONTENT_URI;
private static final Uri THUMB_URI
@@ -202,7 +201,6 @@ public class ImageManager {
}
private static class AddImageCancelable extends BaseCancelable<Void> {
- private ICancelable<Boolean> mSaveImageCancelable;
private final Uri mUri;
private final ContentResolver mCr;
private final int mOrientation;
@@ -211,7 +209,7 @@ public class ImageManager {
public AddImageCancelable(Uri uri, ContentResolver cr,
int orientation, Bitmap source, byte[] jpegData) {
- if (source == null && jpegData == null) {
+ if (source == null && jpegData == null || uri == null) {
throw new IllegalArgumentException("source cannot be null");
}
mUri = uri;
@@ -222,60 +220,46 @@ public class ImageManager {
}
@Override
- public boolean doCancelWork() {
- if (mSaveImageCancelable != null) {
- mSaveImageCancelable.cancel();
- }
- return true;
- }
-
- public Void get() {
+ protected Void execute() throws InterruptedException,
+ ExecutionException {
+ boolean complete = false;
try {
- synchronized (this) {
- if (mCancel) {
- throw new CanceledException();
- }
- }
long id = ContentUris.parseId(mUri);
-
BaseImageList il = new ImageList(mCr, STORAGE_URI,
THUMB_URI, SORT_ASCENDING, null);
Image image = new Image(id, 0, mCr, il, il.getCount(), 0);
String[] projection = new String[] {
ImageColumns._ID,
ImageColumns.MINI_THUMB_MAGIC, ImageColumns.DATA};
-
Cursor c = mCr.query(mUri, projection, null, null, null);
+ String filepath;
try {
c.moveToPosition(0);
- synchronized (this) {
- checkCanceled();
- mSaveImageCancelable = image.saveImageContents(
- mSource, mJpegData, mOrientation, true,
- c.getString(2));
- }
+ filepath = c.getString(2);
} finally {
c.close();
}
+ runSubTask(image.saveImageContents(
+ mSource, mJpegData, mOrientation, true, filepath));
- if (mSaveImageCancelable.get()) {
- ContentValues values = new ContentValues();
- values.put(ImageColumns.MINI_THUMB_MAGIC, 0);
- mCr.update(mUri, values, null, null);
- } else {
- throw new CanceledException();
- }
- } catch (CanceledException ex) {
- if (mUri != null) {
- mCr.delete(mUri, null, null);
+ ContentValues values = new ContentValues();
+ values.put(ImageColumns.MINI_THUMB_MAGIC, 0);
+ mCr.update(mUri, values, null, null);
+ complete = true;
+ return null;
+ } finally {
+ if (!complete) {
+ try {
+ mCr.delete(mUri, null, null);
+ } catch (Throwable t) {
+ // ignore it while clean up.
+ }
}
- acknowledgeCancel();
}
- return null;
}
}
- public static ICancelable<Void> storeImage(
+ public static Cancelable<Void> storeImage(
Uri uri, ContentResolver cr, int orientation,
Bitmap source, byte [] jpegData) {
return new AddImageCancelable(
diff --git a/src/com/android/camera/PriorityTask.java b/src/com/android/camera/PriorityTask.java
index db8ed3c..a060071 100644
--- a/src/com/android/camera/PriorityTask.java
+++ b/src/com/android/camera/PriorityTask.java
@@ -16,6 +16,8 @@
package com.android.camera;
+import com.android.camera.gallery.Cancelable;
+
import android.os.SystemClock;
import android.util.Log;
@@ -33,7 +35,7 @@ import java.util.concurrent.atomic.AtomicLong;
* @param <T> the type of the return value.
*/
public abstract class PriorityTask<T>
- implements Runnable, Comparable<PriorityTask<?>> {
+ implements Cancelable<T>, Runnable, Comparable<PriorityTask<?>> {
private static final String TAG = "PriorityTask";
private static final int STATE_INITIAL = (1 << 0);
@@ -173,9 +175,8 @@ public abstract class PriorityTask<T>
/**
* Requests the task to be canceled.
*
- * @return true if the task has not been canceled but will be canceled;
- * false otherwise (usually the task has been complete/failed/
- * canceled.
+ * @return true if the task is running and has not been requested for
+ * cancel.
*/
public synchronized boolean requestCancel() {
if (mState == STATE_EXECUTING) {
@@ -285,6 +286,10 @@ public abstract class PriorityTask<T>
return true;
}
+ public synchronized void await() throws InterruptedException {
+ await(0);
+ }
+
// used only by PriorityTaskQueue#remove
void resetState() {
mState = STATE_INITIAL;
diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java
index 0d44e09..8053151 100644
--- a/src/com/android/camera/ViewImage.java
+++ b/src/com/android/camera/ViewImage.java
@@ -16,7 +16,7 @@
package com.android.camera;
-import com.android.camera.gallery.ICancelable;
+import com.android.camera.gallery.Cancelable;
import com.android.camera.gallery.IImage;
import com.android.camera.gallery.IImageList;
@@ -50,6 +50,8 @@ import android.widget.Toast;
import android.widget.ZoomButtonsController;
import java.util.Random;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
// This activity can display a whole picture and navigate them in a specific
// gallery. It has two modes: normal mode and slide show mode. In normal mode
@@ -68,7 +70,7 @@ public class ViewImage extends Activity implements View.OnClickListener {
LocalHandler mHandler = new LocalHandler();
- private Random mRandom = new Random(System.currentTimeMillis());
+ private final Random mRandom = new Random(System.currentTimeMillis());
private int [] mShuffleOrder;
private boolean mUseShuffleOrder = false;
private boolean mSlideShowLoop = false;
@@ -94,10 +96,10 @@ public class ViewImage extends Activity implements View.OnClickListener {
private SharedPreferences mPrefs;
private View mNextImageView, mPrevImageView;
- private Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F);
- private Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F);
- private Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F);
- private Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F);
+ private final Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F);
+ private final Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F);
+ private final Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F);
+ private final Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F);
static final int PADDING = 20;
static final int HYSTERESIS = PADDING * 2;
@@ -106,7 +108,7 @@ public class ViewImage extends Activity implements View.OnClickListener {
IImageList mAllImages;
private int mSlideShowImageCurrent = 0;
- private ImageViewTouchBase [] mSlideShowImageViews =
+ private final ImageViewTouchBase [] mSlideShowImageViews =
new ImageViewTouchBase[2];
GestureDetector mGestureDetector;
@@ -1079,7 +1081,7 @@ public class ViewImage extends Activity implements View.OnClickListener {
}
class ImageViewTouch extends ImageViewTouchBase {
- private ViewImage mViewImage;
+ private final ViewImage mViewImage;
private boolean mEnableTrackballScroll;
public ImageViewTouch(Context context) {
@@ -1239,7 +1241,7 @@ class ImageGetter {
private static final String TAG = "ImageGetter";
// The thread which does the work.
- private Thread mGetterThread;
+ private final Thread mGetterThread;
// The base position that's being retrieved. The actual images retrieved
// are this base plus each of the offets.
@@ -1250,7 +1252,7 @@ class ImageGetter {
// This is the loader cancelable that gets set while we're loading an image.
// If we change position we can cancel the current load using this.
- private ICancelable<Bitmap> mLoad;
+ private Cancelable<Bitmap> mLoad;
// True if we're canceling the current load.
private boolean mCancelCurrent = false;
@@ -1268,9 +1270,9 @@ class ImageGetter {
synchronized (this) {
if (!mReady) {
mCancelCurrent = true;
- ICancelable<Bitmap> load = mLoad;
+ Cancelable<Bitmap> load = mLoad;
if (load != null) {
- load.cancel();
+ load.requestCancel();
}
mCancelCurrent = false;
}
@@ -1359,7 +1361,16 @@ class ImageGetter {
if (mLoad != null) {
// The return value could be null if the
// bitmap is too big, or we cancelled it.
- Bitmap b = mLoad.get();
+ Bitmap b;
+ try {
+ b = mLoad.get();
+ } catch (InterruptedException e) {
+ b = null;
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } catch (CancellationException e) {
+ b = null;
+ }
mLoad = null;
if (b != null) {
if (isCanceled()) {
@@ -1452,7 +1463,7 @@ class BitmapCache implements ImageViewTouchBase.Recycler {
}
}
- private Entry[] mCache;
+ private final Entry[] mCache;
public BitmapCache(int size) {
mCache = new Entry[size];
diff --git a/src/com/android/camera/gallery/BaseCancelable.java b/src/com/android/camera/gallery/BaseCancelable.java
index 9b9bf68..fd9779f 100644
--- a/src/com/android/camera/gallery/BaseCancelable.java
+++ b/src/com/android/camera/gallery/BaseCancelable.java
@@ -16,53 +16,164 @@
package com.android.camera.gallery;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
/**
- * A base class for the interface <code>ICancelable</code>.
+ * An abstract class for the interface <code>Cancelable</code>. Subclass can
+ * simply override the <code>execute()</code> function to provide an
+ * implementation of <code>Cancelable</code>.
*/
-public abstract class BaseCancelable<T> implements ICancelable<T> {
- protected boolean mCancel = false;
- protected boolean mFinished = false;
+public abstract class BaseCancelable<T> implements Cancelable<T> {
- /*
- * Subclasses should call acknowledgeCancel when they're finished with
- * their operation.
+ /**
+ * The state of the task, possible transitions are:
+ * <pre>
+ * INITIAL -> CANCELED
+ * EXECUTING -> COMPLETE, CANCELING, ERROR, CANCELED
+ * CANCELING -> CANCELED
+ * </pre>
+ * When the task stop, it must be end with one of the following states:
+ * COMPLETE, CANCELED, or ERROR;
*/
- protected synchronized void acknowledgeCancel() {
- mFinished = true;
- if (mCancel) {
- this.notify();
+ private static final int STATE_INITIAL = (1 << 0);
+ private static final int STATE_EXECUTING = (1 << 1);
+ private static final int STATE_CANCELING = (1 << 2);
+ private static final int STATE_CANCELED = (1 << 3);
+ private static final int STATE_ERROR = (1 << 4);
+ private static final int STATE_COMPLETE = (1 << 5);
+
+ private int mState = STATE_INITIAL;
+
+ private Throwable mError;
+ private T mResult;
+ private Cancelable<?> mCurrentTask;
+ private Thread mThread;
+
+ protected abstract T execute() throws Exception;
+
+ protected synchronized void interruptNow() {
+ if (isInStates(STATE_CANCELING | STATE_EXECUTING)) {
+ mThread.interrupt();
}
}
- public synchronized boolean cancel() {
- if (mCancel || mFinished) {
- return false;
+ /**
+ * Frees the result (which is not null) when the task has been canceled.
+ */
+ protected void freeCanceledResult(T result) {
+ // Do nothing by default;
+ }
+
+ private boolean isInStates(int states) {
+ return (states & mState) != 0;
+ }
+
+ private T handleTerminalStates() throws ExecutionException {
+ if (mState == STATE_CANCELED) {
+ throw new CancellationException();
+ }
+ if (mState == STATE_ERROR) {
+ throw new ExecutionException(mError);
+ }
+ if (mState == STATE_COMPLETE) return mResult;
+ throw new IllegalStateException();
+ }
+
+ public synchronized void await() throws InterruptedException {
+ while (!isInStates(STATE_COMPLETE | STATE_CANCELED | STATE_ERROR)) {
+ wait();
+ }
+ }
+
+ public final T get() throws InterruptedException, ExecutionException {
+ synchronized (this) {
+ if (mState != STATE_INITIAL) {
+ await();
+ return handleTerminalStates();
+ }
+ mThread = Thread.currentThread();
+ mState = STATE_EXECUTING;
}
- mCancel = true;
- boolean retVal = doCancelWork();
try {
- this.wait();
- } catch (InterruptedException ex) {
- throw new RuntimeException(ex);
+ mResult = execute();
+ } catch (CancellationException e) {
+ mState = STATE_CANCELED;
+ } catch (InterruptedException e) {
+ mState = STATE_CANCELED;
+ } catch (Throwable error) {
+ synchronized (this) {
+ if (mState != STATE_CANCELING) {
+ mError = error;
+ mState = STATE_ERROR;
+ }
+ }
+ }
+ synchronized (this) {
+ if (mState == STATE_CANCELING) mState = STATE_CANCELED;
+ if (mState == STATE_EXECUTING) mState = STATE_COMPLETE;
+ notifyAll();
+ if (mState == STATE_CANCELED && mResult != null) {
+ freeCanceledResult(mResult);
+ }
+ return handleTerminalStates();
}
- return retVal;
}
- /*
- * Subclasses can call this to see if they have been canceled.
- * This is the polling model.
+ /**
+ * Requests the task to be canceled.
+ *
+ * @return true if the task is running and has not been canceled; false
+ * otherwise
*/
- protected synchronized void checkCanceled() throws CanceledException {
- if (mCancel) {
- throw new CanceledException();
+
+ public synchronized boolean requestCancel() {
+ if (mState == STATE_INITIAL) {
+ mState = STATE_CANCELED;
+ notifyAll();
+ return false;
}
+ if (mState == STATE_EXECUTING) {
+ if (mCurrentTask != null) mCurrentTask.requestCancel();
+ mState = STATE_CANCELING;
+ return true;
+ }
+ return false;
}
- /*
- * Subclasses implement this method to take whatever action
- * is necessary when getting canceled. Sometimes it's not
- * possible to do anything in which case the "checkCanceled"
- * polling model may be used (or some combination).
+ /**
+ * Whether the task's has been requested for cancel.
*/
- protected abstract boolean doCancelWork();
-} \ No newline at end of file
+ protected synchronized boolean isCanceling() {
+ return mState == STATE_CANCELING;
+ }
+
+ /**
+ * Runs a <code>Cancelable</code> subtask. This method is helpful, if the
+ * task can be composed of several cancelable tasks. By using this function,
+ * it will pass <code>requestCancel</code> message to those subtasks.
+ *
+ * @param <T> the return type of the sub task
+ * @param cancelable the sub task
+ * @return the result of the subtask
+ */
+ protected <T> T runSubTask(Cancelable<T> cancelable)
+ throws InterruptedException, ExecutionException {
+ synchronized (this) {
+ if (mCurrentTask != null) {
+ throw new IllegalStateException(
+ "cannot two subtasks at the same time");
+ }
+ if (mState == STATE_CANCELING) throw new CancellationException();
+ mCurrentTask = cancelable;
+ }
+ try {
+ return cancelable.get();
+ } finally {
+ synchronized (this) {
+ mCurrentTask = null;
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/camera/gallery/BaseImage.java b/src/com/android/camera/gallery/BaseImage.java
index 4bd542a..fbb7239 100644
--- a/src/com/android/camera/gallery/BaseImage.java
+++ b/src/com/android/camera/gallery/BaseImage.java
@@ -80,19 +80,21 @@ public abstract class BaseImage implements IImage {
}
@Override
- public boolean doCancelWork() {
- if (mOutputStream != null) {
- mOutputStream.close();
+ public boolean requestCancel() {
+ if (super.requestCancel()) {
+ if (mOutputStream != null) {
+ mOutputStream.close();
+ }
return true;
}
return false;
}
- public Boolean get() {
+ @Override
+ public Boolean execute() {
try {
OutputStream delegate = mContentResolver.openOutputStream(mUri);
synchronized (this) {
- checkCanceled();
mOutputStream = new ThreadSafeOutputStream(delegate);
}
if (mBitmap != null) {
@@ -103,13 +105,10 @@ public abstract class BaseImage implements IImage {
return true;
} catch (FileNotFoundException ex) {
return false;
- } catch (CanceledException ex) {
- return false;
} catch (IOException ex) {
return false;
} finally {
Util.closeSiliently(mOutputStream);
- acknowledgeCancel();
}
}
}
@@ -122,7 +121,7 @@ public abstract class BaseImage implements IImage {
* @param uri where to store the bitmap
* @return true if we succeeded
*/
- protected ICancelable<Boolean> compressImageToFile(
+ protected Cancelable<Boolean> compressImageToFile(
Bitmap bitmap, byte [] jpegData, Uri uri) {
return new CompressImageToFile(bitmap, jpegData, uri);
}
@@ -167,12 +166,16 @@ public abstract class BaseImage implements IImage {
}
@Override
- public boolean doCancelWork() {
- mOptions.requestCancelDecode();
- return true;
+ public boolean requestCancel() {
+ if (super.requestCancel()) {
+ mOptions.requestCancelDecode();
+ return true;
+ }
+ return false;
}
- public Bitmap get() {
+ @Override
+ protected Bitmap execute() {
try {
Bitmap b = Util.makeBitmap(
mTargetWidthHeight, fullSizeImageUri(),
@@ -185,14 +188,12 @@ public abstract class BaseImage implements IImage {
return null;
} catch (Error e) {
return null;
- } finally {
- acknowledgeCancel();
}
}
}
- public ICancelable<Bitmap> fullSizeBitmapCancelable(
+ public Cancelable<Bitmap> fullSizeBitmapCancelable(
int targetWidthHeight) {
try {
ParcelFileDescriptor pfdInput = mContentResolver
diff --git a/src/com/android/camera/gallery/Cancelable.java b/src/com/android/camera/gallery/Cancelable.java
new file mode 100644
index 0000000..79a7d1b
--- /dev/null
+++ b/src/com/android/camera/gallery/Cancelable.java
@@ -0,0 +1,56 @@
+/*
+ * 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 java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * The interface for all the tasks that could be canceled.
+ */
+public interface Cancelable<T> {
+ /*
+ * Requests this <code>Cancelable</code> to be canceled. This function will
+ * return <code>true</code> if and only if the task is originally running
+ * and now begin requested for cancel.
+ *
+ * If subclass need to do more things to cancel the task. It can override
+ * the code like this:
+ * <pre>
+ * @Override
+ * public boolean requestCancel() {
+ * if (super.requestCancel()) {
+ * // do necessary work to cancel the task
+ * return true;
+ * }
+ * return false;
+ * }
+ * </pre>
+ */
+ public boolean requestCancel();
+
+ public void await() throws InterruptedException;
+
+ /**
+ * Gets the results of this <code>Cancelable</code> task.
+ *
+ * @throws CancellationException if the task has been cancelled
+ * @throws ExecutionException if exception is thrown during the execution of
+ * the task
+ */
+ public T get() throws InterruptedException, ExecutionException;
+} \ No newline at end of file
diff --git a/src/com/android/camera/gallery/CanceledException.java b/src/com/android/camera/gallery/CanceledException.java
deleted file mode 100644
index 5daa48c..0000000
--- a/src/com/android/camera/gallery/CanceledException.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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;
-
-/**
- * Exception which will be thrown when the task has been canceled.
- */
-public class CanceledException extends Exception {
-} \ No newline at end of file
diff --git a/src/com/android/camera/gallery/ICancelable.java b/src/com/android/camera/gallery/ICancelable.java
deleted file mode 100644
index 9c9e1bf..0000000
--- a/src/com/android/camera/gallery/ICancelable.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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;
-
-/**
- * The interface for all the tasks that could be canceled.
- */
-public interface ICancelable<T> {
- /*
- * call cancel() when the unit of work in progress needs to be
- * canceled. This should return true if it was possible to
- * cancel and false otherwise. If this returns false the caller
- * may still be able to cleanup and simulate cancelation.
- */
- public boolean cancel();
-
- public T get();
-} \ No newline at end of file
diff --git a/src/com/android/camera/gallery/IImage.java b/src/com/android/camera/gallery/IImage.java
index 921f40b..9c47d13 100644
--- a/src/com/android/camera/gallery/IImage.java
+++ b/src/com/android/camera/gallery/IImage.java
@@ -35,7 +35,7 @@ public interface IImage {
public abstract Bitmap fullSizeBitmap(int targetWidthOrHeight);
/** Get the cancelable object for the bitmap of the full size image. */
- public abstract ICancelable<Bitmap> fullSizeBitmapCancelable(
+ public abstract Cancelable<Bitmap> fullSizeBitmapCancelable(
int targetWidthOrHeight);
/** Get the input stream associated with a given full size image. */
diff --git a/src/com/android/camera/gallery/Image.java b/src/com/android/camera/gallery/Image.java
index 2d60a47..58d9948 100644
--- a/src/com/android/camera/gallery/Image.java
+++ b/src/com/android/camera/gallery/Image.java
@@ -33,6 +33,7 @@ import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.concurrent.ExecutionException;
/**
* The class for normal images in gallery.
@@ -171,12 +172,11 @@ public class Image extends BaseImage implements IImage {
mExifData.put(tag, value);
}
- private class SaveImageContentsCancelable extends BaseCancelable<Boolean> {
+ private class SaveImageContentsCancelable extends BaseCancelable<Void> {
private final Bitmap mImage;
private final byte [] mJpegData;
private final int mOrientation;
private final String mFilePath;
- ICancelable<Boolean> mCurrentCancelable = null;
SaveImageContentsCancelable(Bitmap image, byte[] jpegData,
int orientation, String filePath) {
@@ -187,80 +187,54 @@ public class Image extends BaseImage implements IImage {
}
@Override
- public boolean doCancelWork() {
- synchronized (this) {
- if (mCurrentCancelable != null) mCurrentCancelable.cancel();
- }
- return true;
- }
+ public Void execute() throws InterruptedException, ExecutionException {
+ Bitmap thumbnail = null;
+ Uri uri = mContainer.contentUri(mId);
+ runSubTask(compressImageToFile(mImage, mJpegData, uri));
- public Boolean get() {
- try {
- Bitmap thumbnail = null;
-
- Uri uri = mContainer.contentUri(mId);
- synchronized (this) {
- checkCanceled();
- mCurrentCancelable =
- compressImageToFile(mImage, mJpegData, uri);
+ synchronized (this) {
+ String filePath = mFilePath;
+
+ // TODO: If thumbData is present and usable, we should call
+ // the version of storeThumbnail which takes a byte array,
+ // rather than re-encoding a new JPEG of the same
+ // dimensions.
+ byte[] thumbData = null;
+ synchronized (ExifInterface.class) {
+ thumbData =
+ (new ExifInterface(filePath)).getThumbnail();
}
- if (!mCurrentCancelable.get()) return false;
-
- synchronized (this) {
- String filePath = mFilePath;
-
- // TODO: If thumbData is present and usable, we should call
- // the version of storeThumbnail which takes a byte array,
- // rather than re-encoding a new JPEG of the same
- // dimensions.
- byte[] thumbData = null;
- synchronized (ExifInterface.class) {
- thumbData =
- (new ExifInterface(filePath)).getThumbnail();
- }
-
- if (thumbData != null) {
- thumbnail = BitmapFactory.decodeByteArray(
- thumbData, 0, thumbData.length);
- }
- if (thumbnail == null && mImage != null) {
- thumbnail = mImage;
- }
- if (thumbnail == null && mJpegData != null) {
- thumbnail = BitmapFactory.decodeByteArray(
- mJpegData, 0, mJpegData.length);
- }
+ if (thumbData != null) {
+ thumbnail = BitmapFactory.decodeByteArray(
+ thumbData, 0, thumbData.length);
}
+ if (thumbnail == null && mImage != null) {
+ thumbnail = mImage;
+ }
+ if (thumbnail == null && mJpegData != null) {
+ thumbnail = BitmapFactory.decodeByteArray(
+ mJpegData, 0, mJpegData.length);
+ }
+ }
- mContainer.storeThumbnail(
- thumbnail, Image.this.fullSizeImageId());
- checkCanceled();
+ mContainer.storeThumbnail(
+ thumbnail, Image.this.fullSizeImageId());
+ if (isCanceling()) return null;
- try {
- thumbnail = Util.rotate(thumbnail, mOrientation);
- saveMiniThumb(thumbnail);
- } catch (IOException e) {
- // Ignore if unable to save thumb.
- }
- checkCanceled();
- return true;
- } catch (CanceledException ex) {
- // Got canceled... need to cleanup.
- return false;
- } finally {
- /*
- * Cursor c = getCursor(); synchronized (c) { if
- * (c.moveTo(getRow())) { mContainer.requery(); } }
- */
- acknowledgeCancel();
+ try {
+ thumbnail = Util.rotate(thumbnail, mOrientation);
+ saveMiniThumb(thumbnail);
+ } catch (IOException e) {
+ // Ignore if unable to save thumb.
+ Log.e(TAG, "unable to rotate / save thumb", e);
}
+ return null;
}
}
- public ICancelable<Boolean> saveImageContents(Bitmap image,
- byte [] jpegData, int orientation, boolean newFile,
- String filePath) {
+ public Cancelable<Void> saveImageContents(Bitmap image, byte [] jpegData,
+ int orientation, boolean newFile, String filePath) {
return new SaveImageContentsCancelable(
image, jpegData, orientation, filePath);
}
diff --git a/src/com/android/camera/gallery/UriImage.java b/src/com/android/camera/gallery/UriImage.java
index dddde36..0232a90 100644
--- a/src/com/android/camera/gallery/UriImage.java
+++ b/src/com/android/camera/gallery/UriImage.java
@@ -112,12 +112,16 @@ class UriImage implements IImage {
}
@Override
- public boolean doCancelWork() {
- mOptions.requestCancelDecode();
- return true;
+ public boolean requestCancel() {
+ if (super.requestCancel()) {
+ mOptions.requestCancelDecode();
+ return true;
+ }
+ return false;
}
- public Bitmap get() {
+ @Override
+ protected Bitmap execute() {
try {
Bitmap b = Util.makeBitmap(mTargetWidthOrHeight,
fullSizeImageUri(), mContentResolver, mPfdInput,
@@ -125,13 +129,11 @@ class UriImage implements IImage {
return b;
} catch (Exception ex) {
return null;
- } finally {
- acknowledgeCancel();
}
}
}
- public ICancelable<Bitmap> fullSizeBitmapCancelable(
+ public Cancelable<Bitmap> fullSizeBitmapCancelable(
int targetWidthOrHeight) {
try {
ParcelFileDescriptor pfdInput = getPFD();
diff --git a/src/com/android/camera/gallery/VideoObject.java b/src/com/android/camera/gallery/VideoObject.java
index 25d62d7..a5ed17e 100644
--- a/src/com/android/camera/gallery/VideoObject.java
+++ b/src/com/android/camera/gallery/VideoObject.java
@@ -78,7 +78,7 @@ public class VideoObject extends BaseImage implements IImage {
}
@Override
- public ICancelable<Bitmap> fullSizeBitmapCancelable(
+ public Cancelable<Bitmap> fullSizeBitmapCancelable(
int targetWidthHeight) {
return null;
}
diff --git a/tests/src/com/android/camera/gallery/BaseCancelableTest.java b/tests/src/com/android/camera/gallery/BaseCancelableTest.java
new file mode 100644
index 0000000..330e2f1
--- /dev/null
+++ b/tests/src/com/android/camera/gallery/BaseCancelableTest.java
@@ -0,0 +1,232 @@
+package com.android.camera.gallery;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+public class BaseCancelableTest extends AndroidTestCase {
+ private static final String TAG = "BaseCancelableTest";
+
+ private static class TestTask extends BaseCancelable<Integer> {
+
+ private boolean mDone = false;
+ private boolean mCancel = false;
+ private boolean mRunning = false;
+ private boolean mFireError = false;
+
+ @Override
+ public synchronized boolean requestCancel() {
+ if (super.requestCancel()) {
+ mCancel = true;
+ notifyAll();
+ return true;
+ }
+ return false;
+ }
+
+ public synchronized void waitUntilRunning()
+ throws InterruptedException {
+ while (!mRunning) {
+ wait();
+ }
+ }
+
+ @Override
+ protected synchronized Integer execute() throws Exception {
+ mRunning = true;
+ notifyAll();
+
+ while (!mDone && !mCancel) {
+ wait();
+ }
+ if (mFireError) throw new IllegalStateException();
+ return 0;
+ }
+
+ public synchronized void completeTask() {
+ mDone = true;
+ notifyAll();
+ }
+
+ public synchronized void completeTaskWithException() {
+ mDone = true;
+ mFireError = true;
+ notifyAll();
+ }
+ }
+
+ public void testSimpleFlow() throws Exception {
+ TestTask task = new TestTask();
+ task.completeTask();
+ assertEquals(0, task.get().intValue());
+ }
+
+ private void assertCancellationException(BaseCancelable<?> task)
+ throws Exception {
+ try {
+ task.get();
+ fail("expect a " + CancellationException.class);
+ } catch (CancellationException e) {
+ // expected
+ }
+ }
+
+ private void assertExecutionException(BaseCancelable<?> task)
+ throws Exception {
+ try {
+ task.get();
+ fail("expect a " + ExecutionException.class);
+ } catch (ExecutionException e) {
+ // expected
+ }
+ }
+
+ public void testCancelInInitialState() throws Exception {
+ TestTask task = new TestTask();
+ task.requestCancel();
+ assertCancellationException(task);
+ }
+
+ public void testCancelInRunningState() throws Exception {
+ final TestTask task = new TestTask();
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ task.waitUntilRunning();
+ assertTrue(task.requestCancel());
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupt", e);
+ }
+ }
+ }.start();
+ assertCancellationException(task);
+ }
+
+ public void testConcurrentGet() throws Exception {
+ final TestTask task = new TestTask();
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ assertEquals(0, task.get().intValue());
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupt", e);
+ } catch (ExecutionException e) {
+ Log.e(TAG, "execution fail: ", e);
+ }
+ }
+ }.start();
+ task.waitUntilRunning();
+ task.completeTask();
+ assertEquals(0, task.get().intValue());
+ }
+
+ public void testConcurrentGetOnCancled() throws Exception {
+ final TestTask task = new TestTask();
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ assertCancellationException(task);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception", e);
+ }
+ }
+ }.start();
+ task.waitUntilRunning();
+ task.requestCancel();
+ assertCancellationException(task);
+ }
+
+ private static class AddTask extends BaseCancelable<Integer> {
+ private final Cancelable<Integer> mTasks[];
+
+ public AddTask(Cancelable<Integer> ... tasks) {
+ mTasks = tasks.clone();
+ }
+
+ @Override
+ protected Integer execute() throws Exception {
+ int sum = 0;
+ for (int i = 0, n = mTasks.length; i < n; ++i) {
+ sum += runSubTask(mTasks[i]);
+ }
+ return sum;
+ }
+ }
+
+ public void testExecuteSubtask() throws Exception {
+ TestTask subtask = new TestTask();
+ @SuppressWarnings("unchecked")
+ AddTask addTask = new AddTask(subtask);
+ subtask.completeTask();
+ assertEquals(0, addTask.get().intValue());
+ }
+
+ public void testExecuteSubtaskWithError() throws Exception {
+ TestTask subtask = new TestTask();
+ @SuppressWarnings("unchecked")
+ AddTask addTask = new AddTask(subtask);
+ subtask.completeTaskWithException();
+ assertExecutionException(addTask);
+ }
+
+ public void testCancelWithSubtask() throws Exception {
+ final TestTask subtask = new TestTask();
+ @SuppressWarnings("unchecked")
+ final AddTask addTask = new AddTask(subtask);
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ subtask.waitUntilRunning();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupted", e);
+ }
+ addTask.requestCancel();
+ }
+ }.start();
+ assertCancellationException(addTask);
+ }
+
+ private static class TaskSet extends BaseCancelable<Integer> {
+ private final TestTask mTasks[];
+ private int mExecutedTaskCount = 0;
+
+ public TaskSet(TestTask ... tasks) {
+ mTasks = tasks.clone();
+ }
+
+ @Override
+ protected Integer execute() throws Exception {
+ int exceptionCount = 0;
+ for (TestTask task : mTasks) {
+ try {
+ ++ mExecutedTaskCount;
+ runSubTask(task);
+ } catch (ExecutionException e) {
+ ++ exceptionCount;
+ }
+ }
+ return exceptionCount;
+ }
+
+ }
+
+ public void testHandleExceptionInSubTasks() throws Exception {
+ TestTask task0 = new TestTask();
+ TestTask task1 = new TestTask();
+ TestTask task2 = new TestTask();
+
+ task0.completeTask();
+ task1.completeTaskWithException();
+ task2.completeTask();
+ TaskSet taskSet = new TaskSet(task0, task1, task2);
+
+ assertEquals(1, taskSet.get().intValue());
+ assertEquals(3, taskSet.mExecutedTaskCount);
+ }
+}