| /* |
| * Copyright (C) 2011 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.emailcommon.utility; |
| |
| import android.os.AsyncTask; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * {@link AsyncTask} substitution for the email app. |
| * |
| * Modeled after {@link AsyncTask}; the basic usage is the same, with extra features: |
| * - Bulk cancellation of multiple tasks. This is mainly used by UI to cancel pending tasks |
| * in onDestroy() or similar places. |
| * - Instead of {@link AsyncTask#onPostExecute}, it has {@link #onSuccess(Object)}, as the |
| * regular {@link AsyncTask#onPostExecute} is a bit hard to predict when it'll be called and |
| * when it won't. |
| * |
| * Note this class is missing some of the {@link AsyncTask} features, e.g. it lacks |
| * {@link AsyncTask#onProgressUpdate}. Add these when necessary. |
| */ |
| public abstract class EmailAsyncTask<Params, Progress, Result> { |
| private static final Executor SERIAL_EXECUTOR = AsyncTask.SERIAL_EXECUTOR; |
| private static final Executor PARALLEL_EXECUTOR = AsyncTask.THREAD_POOL_EXECUTOR; |
| |
| /** |
| * Tracks {@link EmailAsyncTask}. |
| * |
| * Call {@link #cancelAllInterrupt()} to cancel all tasks registered. |
| */ |
| public static class Tracker { |
| private final LinkedList<EmailAsyncTask<?, ?, ?>> mTasks = |
| new LinkedList<EmailAsyncTask<?, ?, ?>>(); |
| |
| private void add(EmailAsyncTask<?, ?, ?> task) { |
| synchronized (mTasks) { |
| mTasks.add(task); |
| } |
| } |
| |
| private void remove(EmailAsyncTask<?, ?, ?> task) { |
| synchronized (mTasks) { |
| mTasks.remove(task); |
| } |
| } |
| |
| /** |
| * Cancel all registered tasks. |
| */ |
| @VisibleForTesting |
| public void cancelAllInterrupt() { |
| synchronized (mTasks) { |
| for (EmailAsyncTask<?, ?, ?> task : mTasks) { |
| task.cancel(true); |
| } |
| mTasks.clear(); |
| } |
| } |
| |
| /** |
| * Cancel all instances of the same class as {@code current} other than |
| * {@code current} itself. |
| */ |
| /* package */ void cancelOthers(EmailAsyncTask<?, ?, ?> current) { |
| final Class<?> clazz = current.getClass(); |
| synchronized (mTasks) { |
| final ArrayList<EmailAsyncTask<?, ?, ?>> toRemove = |
| new ArrayList<EmailAsyncTask<?, ?, ?>>(); |
| for (EmailAsyncTask<?, ?, ?> task : mTasks) { |
| if ((task != current) && task.getClass().equals(clazz)) { |
| task.cancel(true); |
| toRemove.add(task); |
| } |
| } |
| for (EmailAsyncTask<?, ?, ?> task : toRemove) { |
| mTasks.remove(task); |
| } |
| } |
| } |
| |
| /* package */ int getTaskCountForTest() { |
| return mTasks.size(); |
| } |
| |
| /* package */ boolean containsTaskForTest(EmailAsyncTask<?, ?, ?> task) { |
| return mTasks.contains(task); |
| } |
| } |
| |
| private final Tracker mTracker; |
| |
| private static class InnerTask<Params2, Progress2, Result2> |
| extends AsyncTask<Params2, Progress2, Result2> { |
| private final EmailAsyncTask<Params2, Progress2, Result2> mOwner; |
| |
| public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) { |
| mOwner = owner; |
| } |
| |
| @Override |
| protected Result2 doInBackground(Params2... params) { |
| return mOwner.doInBackground(params); |
| } |
| |
| @Override |
| public void onCancelled(Result2 result) { |
| mOwner.unregisterSelf(); |
| mOwner.onCancelled(result); |
| } |
| |
| @Override |
| public void onPostExecute(Result2 result) { |
| mOwner.unregisterSelf(); |
| if (mOwner.mCancelled) { |
| mOwner.onCancelled(result); |
| } else { |
| mOwner.onSuccess(result); |
| } |
| } |
| } |
| |
| private final InnerTask<Params, Progress, Result> mInnerTask; |
| private volatile boolean mCancelled; |
| |
| public EmailAsyncTask(Tracker tracker) { |
| mTracker = tracker; |
| if (mTracker != null) { |
| mTracker.add(this); |
| } |
| mInnerTask = new InnerTask<Params, Progress, Result>(this); |
| } |
| |
| /* package */ final void unregisterSelf() { |
| if (mTracker != null) { |
| mTracker.remove(this); |
| } |
| } |
| |
| /** @see AsyncTask#doInBackground */ |
| protected abstract Result doInBackground(Params... params); |
| |
| |
| /** @see AsyncTask#cancel(boolean) */ |
| public final void cancel(boolean mayInterruptIfRunning) { |
| mCancelled = true; |
| mInnerTask.cancel(mayInterruptIfRunning); |
| } |
| |
| /** @see AsyncTask#onCancelled */ |
| protected void onCancelled(Result result) { |
| } |
| |
| /** |
| * Similar to {@link AsyncTask#onPostExecute}, but this will never be executed if |
| * {@link #cancel(boolean)} has been called before its execution, even if |
| * {@link #doInBackground(Object...)} has completed when cancelled. |
| * |
| * @see AsyncTask#onPostExecute |
| */ |
| protected void onSuccess(Result result) { |
| } |
| |
| /** |
| * execute on {@link #PARALLEL_EXECUTOR} |
| * |
| * @see AsyncTask#execute |
| */ |
| public final EmailAsyncTask<Params, Progress, Result> executeParallel(Params... params) { |
| return executeInternal(PARALLEL_EXECUTOR, false, params); |
| } |
| |
| /** |
| * execute on {@link #SERIAL_EXECUTOR} |
| * |
| * @see AsyncTask#execute |
| */ |
| public final EmailAsyncTask<Params, Progress, Result> executeSerial(Params... params) { |
| return executeInternal(SERIAL_EXECUTOR, false, params); |
| } |
| |
| /** |
| * Cancel all previously created instances of the same class tracked by the same |
| * {@link Tracker}, and then {@link #executeParallel}. |
| */ |
| public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteParallel( |
| Params... params) { |
| return executeInternal(PARALLEL_EXECUTOR, true, params); |
| } |
| |
| /** |
| * Cancel all previously created instances of the same class tracked by the same |
| * {@link Tracker}, and then {@link #executeSerial}. |
| */ |
| public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteSerial( |
| Params... params) { |
| return executeInternal(SERIAL_EXECUTOR, true, params); |
| } |
| |
| private EmailAsyncTask<Params, Progress, Result> executeInternal(Executor executor, |
| boolean cancelPrevious, Params... params) { |
| if (cancelPrevious) { |
| if (mTracker == null) { |
| throw new IllegalStateException(); |
| } else { |
| mTracker.cancelOthers(this); |
| } |
| } |
| mInnerTask.executeOnExecutor(executor, params); |
| return this; |
| } |
| |
| /** |
| * Runs a {@link Runnable} in a bg thread, using {@link #PARALLEL_EXECUTOR}. |
| */ |
| public static EmailAsyncTask<Void, Void, Void> runAsyncParallel(Runnable runnable) { |
| return runAsyncInternal(PARALLEL_EXECUTOR, runnable); |
| } |
| |
| /** |
| * Runs a {@link Runnable} in a bg thread, using {@link #SERIAL_EXECUTOR}. |
| */ |
| public static EmailAsyncTask<Void, Void, Void> runAsyncSerial(Runnable runnable) { |
| return runAsyncInternal(SERIAL_EXECUTOR, runnable); |
| } |
| |
| private static EmailAsyncTask<Void, Void, Void> runAsyncInternal(Executor executor, |
| final Runnable runnable) { |
| EmailAsyncTask<Void, Void, Void> task = new EmailAsyncTask<Void, Void, Void>(null) { |
| @Override |
| protected Void doInBackground(Void... params) { |
| runnable.run(); |
| return null; |
| } |
| }; |
| return task.executeInternal(executor, false, (Void[]) null); |
| } |
| |
| /** |
| * Wait until {@link #doInBackground} finishes and returns the results of the computation. |
| * |
| * @see AsyncTask#get |
| */ |
| public final Result get() throws InterruptedException, ExecutionException { |
| return mInnerTask.get(); |
| } |
| |
| /* package */ final Result callDoInBackgroundForTest(Params... params) { |
| return mInnerTask.doInBackground(params); |
| } |
| |
| /* package */ final void callOnCancelledForTest(Result result) { |
| mInnerTask.onCancelled(result); |
| } |
| |
| /* package */ final void callOnPostExecuteForTest(Result result) { |
| mInnerTask.onPostExecute(result); |
| } |
| } |