Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5ba812b9 authored by Romain Guy's avatar Romain Guy
Browse files

Make sure onCancelled() is called if cancel() is called early.

In some situations, when cancel() was called before the task had a
chance to start its execution, onCancelled() would not be invoked.

Change-Id: I6c1f4cd28a209fb8cc779bb212f500565dfceaae
parent 58f750ad
Loading
Loading
Loading
Loading
+59 −21
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;


/**
/**
@@ -115,11 +116,11 @@ import java.util.concurrent.atomic.AtomicInteger;
 * <h2>Cancelling a task</h2>
 * <h2>Cancelling a task</h2>
 * <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
 * <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
 * this method will cause subsequent calls to {@link #isCancelled()} to return true.
 * this method will cause subsequent calls to {@link #isCancelled()} to return true.
 * After invoking this method, {@link #onCancelled()}, instead of {@link #onPostExecute(Object)}
 * After invoking this method, {@link #onCancelled(Object)}, instead of
 * will be invoked after {@link #doInBackground(Object[])} returns. To ensure that
 * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
 * a task is cancelled as quickly as possible, you should always check the return
 * returns. To ensure that a task is cancelled as quickly as possible, you should always
 * value of {@link #isCancelled()} periodically from {@link #doInBackground(Object[])},
 * check the return value of {@link #isCancelled()} periodically from
 * if possible (inside a loop for instance.)</p>
 * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
 *
 *
 * <h2>Threading rules</h2>
 * <h2>Threading rules</h2>
 * <p>There are a few threading rules that must be followed for this class to
 * <p>There are a few threading rules that must be followed for this class to
@@ -174,6 +175,8 @@ public abstract class AsyncTask<Params, Progress, Result> {


    private volatile Status mStatus = Status.PENDING;
    private volatile Status mStatus = Status.PENDING;
    
    
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

    /**
    /**
     * Indicates the current status of the task. Each status will be set only once
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     * during the lifetime of a task.
@@ -204,15 +207,10 @@ public abstract class AsyncTask<Params, Progress, Result> {
    public AsyncTask() {
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
            public Result call() throws Exception {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                mTaskInvoked.set(true);

                Result result = doInBackground(mParams);

                Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                        new AsyncTaskResult<Result>(AsyncTask.this, result));
                message.sendToTarget();


                return result;
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return postResult(doInBackground(mParams));
            }
            }
        };
        };


@@ -220,14 +218,16 @@ public abstract class AsyncTask<Params, Progress, Result> {
            @Override
            @Override
            protected void done() {
            protected void done() {
                try {
                try {
                    get();
                    final Result result = get();

                    postResultIfNotInvoked(result);
                } catch (InterruptedException e) {
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                            e.getCause());
                } catch (CancellationException e) {
                } catch (CancellationException e) {
                    // Taken care of in the WorkerRunnable
                    postResultIfNotInvoked(null);
                } catch (Throwable t) {
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                            + "doInBackground()", t);
@@ -236,6 +236,20 @@ public abstract class AsyncTask<Params, Progress, Result> {
        };
        };
    }
    }


    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

    private Result postResult(Result result) {
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    /**
    /**
     * Returns the current status of this task.
     * Returns the current status of this task.
     *
     *
@@ -282,7 +296,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
     *
     *
     * @see #onPreExecute
     * @see #onPreExecute
     * @see #doInBackground
     * @see #doInBackground
     * @see #onCancelled() 
     * @see #onCancelled(Object) 
     */
     */
    @SuppressWarnings({"UnusedDeclaration"})
    @SuppressWarnings({"UnusedDeclaration"})
    protected void onPostExecute(Result result) {
    protected void onPostExecute(Result result) {
@@ -302,9 +316,33 @@ public abstract class AsyncTask<Params, Progress, Result> {
    }
    }


    /**
    /**
     * Runs on the UI thread after {@link #cancel(boolean)} is invoked and
     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
     * {@link #doInBackground(Object[])} has finished.
     * {@link #doInBackground(Object[])} has finished.</p>
     * 
     * <p>The default implementation simply invokes {@link #onCancelled()} and
     * ignores the result. If you write your own implementation, do not call
     * <code>super.onCancelled(result)</code>.</p>
     *
     * @param result The result, if any, computed in
     *               {@link #doInBackground(Object[])}, can be null
     * 
     * @see #cancel(boolean)
     * @see #isCancelled()
     */
    @SuppressWarnings({"UnusedParameters"})
    protected void onCancelled(Result result) {
        onCancelled();
    }    
    
    /**
     * <p>Applications should preferably override {@link #onCancelled(Object)}.
     * This method is invoked by the default implementation of
     * {@link #onCancelled(Object)}.</p>
     * 
     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
     * {@link #doInBackground(Object[])} has finished.</p>
     *
     *
     * @see #onCancelled(Object) 
     * @see #cancel(boolean)
     * @see #cancel(boolean)
     * @see #isCancelled()
     * @see #isCancelled()
     */
     */
@@ -335,7 +373,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
     * whether the thread executing this task should be interrupted in
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.</p>
     * an attempt to stop the task.</p>
     * 
     * 
     * <p>Calling this method will result in {@link #onCancelled()} being
     * <p>Calling this method will result in {@link #onCancelled(Object)} being
     * invoked on the UI thread after {@link #doInBackground(Object[])}
     * invoked on the UI thread after {@link #doInBackground(Object[])}
     * returns. Calling this method guarantees that {@link #onPostExecute(Object)}
     * returns. Calling this method guarantees that {@link #onPostExecute(Object)}
     * is never invoked. After invoking this method, you should check the
     * is never invoked. After invoking this method, you should check the
@@ -352,7 +390,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
     *         <tt>true</tt> otherwise
     *         <tt>true</tt> otherwise
     *
     *
     * @see #isCancelled()
     * @see #isCancelled()
     * @see #onCancelled()
     * @see #onCancelled(Object)
     */
     */
    public final boolean cancel(boolean mayInterruptIfRunning) {
    public final boolean cancel(boolean mayInterruptIfRunning) {
        return mFuture.cancel(mayInterruptIfRunning);
        return mFuture.cancel(mayInterruptIfRunning);
@@ -452,7 +490,7 @@ public abstract class AsyncTask<Params, Progress, Result> {


    private void finish(Result result) {
    private void finish(Result result) {
        if (isCancelled()) {
        if (isCancelled()) {
            onCancelled();
            onCancelled(result);
        } else {
        } else {
            onPostExecute(result);
            onPostExecute(result);
        }
        }