blob: 82750af10102e5c59137f242af795a77a7ca7d92 [file] [log] [blame]
/*
* 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;
import com.android.camera.gallery.Cancelable;
import android.os.SystemClock;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* Abstract class used for Tasks which can be canceled / change priority. The
* task should be added into a <code>PriorityTaskQueue</code> for execution.
*
* @param <T> the type of the return value.
*/
public abstract class PriorityTask<T>
implements Cancelable<T>, Runnable, Comparable<PriorityTask<?>> {
private static final String TAG = "PriorityTask";
private static final int STATE_INITIAL = (1 << 0);
private static final int STATE_QUEUED = (1 << 1);
private static final int STATE_EXECUTING = (1 << 2);
private static final int STATE_ERROR = (1 << 3);
private static final int STATE_CANCELING = (1 << 4);
private static final int STATE_CANCELED = (1 << 5);
private static final int STATE_COMPLETE = (1 << 6);
public static final int PRIORITY_DEFAULT = 0;
public static final int PRIORITY_BACKGROUND = 10;
public static final int PRIORITY_FOREGROUND = -10;
private static final AtomicLong sCounter = new AtomicLong(0L);
/**
* The state of the task, possible transitions are:
* <pre>
* INITIAL -> QUEUED, CANCELED
* QUEUED -> INITIAL, EXECUTING, CANCELED
* EXECUTING -> COMPLETE, CANCELING, ERROR
* CANCELING -> CANCELED
* </pre>
* When the task stop, it must be end with one of the following states:
* COMPLETE, CANCELED, or ERROR;
*/
private int mState = STATE_INITIAL;
private int mPriority;
private long mTimestamp;
private PriorityTaskQueue mQueue;
private Thread mThread;
private T mResult;
private Throwable mError;
private List<Callback<T>> mCallbacks = new ArrayList<Callback<T>>();
public PriorityTask() {
this(PRIORITY_DEFAULT);
}
public PriorityTask(int priority) {
setPriority(priority);
}
private static class CancelableAdapter<T> extends PriorityTask<T> {
private final Cancelable<T> mTask;
public CancelableAdapter(Cancelable<T> task, int priority) {
super(priority);
mTask = task;
}
@Override
public boolean requestCancel() {
if (super.requestCancel()) {
mTask.requestCancel();
return true;
}
return false;
}
@Override
protected T execute() throws Exception {
return mTask.get();
}
}
public static <T> PriorityTask<T> wrap(Cancelable<T> task, int priority) {
return new CancelableAdapter<T>(task, priority);
}
public static <T> PriorityTask<T> wrap(Cancelable<T> task) {
return new CancelableAdapter<T>(task, PRIORITY_DEFAULT);
}
/**
* Executes the task. Subclasses should override this function. Note: if
* this function throws an <code>InterruptedException</code>, the task will
* be considered as being canceled instead of fail.
*/
protected abstract T execute() throws Exception;
/**
* Frees the result (which is not null) when the task has been canceled.
*/
protected void freeCanceledResult(T result) {
// Do nothing by default;
}
/**
* Invoked when the task has been a terminate state. It could be complete,
* canceled, or failed.
*
* @param <T> the type of result
*/
public static interface Callback<T> {
public void onResultAvailable(PriorityTask<T> t, T result);
public void onFail(PriorityTask<T> t, Throwable error);
public void onCanceled(PriorityTask<T> t);
}
public final void run() {
try {
synchronized (this) {
if (mState == STATE_CANCELED) return;
if (mState != STATE_QUEUED) {
throw new IllegalStateException();
}
mThread = Thread.currentThread();
mState = STATE_EXECUTING;
}
T result = execute();
synchronized (this) {
if (mState == STATE_CANCELING) mState = STATE_CANCELED;
if (mState == STATE_EXECUTING) mState = STATE_COMPLETE;
notifyAll();
}
if (mState == STATE_COMPLETE) {
mResult = result;
fireOnResultAvailable(result);
} else {
if (result != null) freeCanceledResult(result);
fireOnCanceled();
}
} catch (InterruptedException ie) {
synchronized (this) {
mState = STATE_CANCELED;
notifyAll();
}
fireOnCanceled();
} catch (Throwable throwable) {
synchronized (this) {
mError = throwable;
mState = STATE_ERROR;
notifyAll();
}
fireOnFail(throwable);
} finally {
mThread = null;
mQueue = null;
mCallbacks = null;
}
}
/**
* Whether the task's has been requested for cancel.
*/
protected synchronized boolean isCanceling() {
return mState == STATE_CANCELING;
}
public synchronized void setPriority(int priority) {
if (mState != STATE_INITIAL) {
throw new IllegalStateException();
}
if (mPriority == priority) return;
mPriority = priority;
}
protected synchronized void interruptNow() {
if (isInStates(STATE_EXECUTING | STATE_CANCELING)) mThread.interrupt();
}
/**
* Requests the task to be canceled.
*
* @return true if the task is running and has not been requested for
* cancel.
*/
public synchronized boolean requestCancel() {
if (mState == STATE_EXECUTING) {
mState = STATE_CANCELING;
return true;
}
if (isInStates(STATE_QUEUED | STATE_INITIAL)) {
mState = STATE_CANCELED;
notifyAll();
if (mQueue != null) mQueue.removeCanceledTask(this);
fireOnCanceled();
return true;
}
return !isInStates(STATE_COMPLETE | STATE_ERROR | STATE_CANCELED);
}
private void fireOnResultAvailable(T result) {
for (Callback<T> callback : mCallbacks) {
try {
callback.onResultAvailable(this, result);
} catch (Throwable t) {
Log.e(TAG, "Ignore all throwable in callback - " + t);
}
}
}
private void fireOnCanceled() {
for (Callback<T> callback : mCallbacks) {
try {
callback.onCanceled(this);
} catch (Throwable t) {
Log.e(TAG, "Ignore all throwable in callback - " + t);
}
}
}
private void fireOnFail(Throwable error) {
for (Callback<T> callback : mCallbacks) {
try {
callback.onFail(this, error);
} catch (Throwable t) {
Log.e(TAG, "Ignore all throwable in callback - " + t);
}
}
}
private final boolean isInStates(int states) {
return (mState & states) != 0;
}
boolean setupBeforeQueued(PriorityTaskQueue queue) {
if (mState == STATE_CANCELED) return false;
if (mState != STATE_INITIAL) throw new IllegalStateException();
mQueue = queue;
mState = STATE_QUEUED;
mTimestamp = sCounter.getAndIncrement();
return true;
}
public synchronized void addCallback(Callback<T> callback) {
if (!isInStates(STATE_INITIAL)) throw new IllegalStateException();
mCallbacks.add(callback);
}
public synchronized void removeCallback(Callback<T> callback) {
if (!isInStates(STATE_INITIAL)) throw new IllegalStateException();
mCallbacks.remove(callback);
}
public int compareTo(PriorityTask<?> other) {
return (mPriority != other.mPriority)
? (mPriority - other.mPriority)
: (int) (mTimestamp - other.mTimestamp);
}
public T get() throws InterruptedException, ExecutionException {
await(0);
if (mState == STATE_CANCELED) throw new CancellationException();
if (mState == STATE_ERROR) throw new ExecutionException(mError);
return mState == STATE_COMPLETE ? mResult : null;
}
public T get(long timeout)
throws InterruptedException, ExecutionException, TimeoutException {
if (!await(timeout)) throw new TimeoutException();
if (mState == STATE_CANCELED) throw new CancellationException();
if (mState == STATE_ERROR) throw new ExecutionException(mError);
return mState == STATE_COMPLETE ? mResult : null;
}
/**
* Waits the task to be in terminate state.
*
* @param timeout the timeout in milliseconds; 0 for no timeout.
* @return whether the task is terminated
* @throws InterruptedException
*/
public synchronized boolean await(long timeout)
throws InterruptedException {
long now = SystemClock.elapsedRealtime();
long due = now + timeout;
while (!isInStates(STATE_CANCELED | STATE_ERROR | STATE_COMPLETE)) {
wait(timeout == 0 ? 0 : due - now);
now = SystemClock.elapsedRealtime();
if (timeout != 0 && due <= now) return false;
}
return true;
}
public synchronized void await() throws InterruptedException {
await(0);
}
// used only by PriorityTaskQueue#remove
void resetState() {
mState = STATE_INITIAL;
}
}