EmailAsyncTask.java revision feeb1e3adac000f36e1e7a5d754ad6b4e3ab34c1
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.emailcommon.utility;
18
19import android.os.AsyncTask;
20
21import com.google.common.annotations.VisibleForTesting;
22
23import java.util.ArrayList;
24import java.util.LinkedList;
25import java.util.concurrent.ExecutionException;
26import java.util.concurrent.Executor;
27
28/**
29 * {@link AsyncTask} substitution for the email app.
30 *
31 * Modeled after {@link AsyncTask}; the basic usage is the same, with extra features:
32 * - Bulk cancellation of multiple tasks.  This is mainly used by UI to cancel pending tasks
33 *   in onDestroy() or similar places.
34 * - Instead of {@link AsyncTask#onPostExecute}, it has {@link #onSuccess(Object)}, as the
35 *   regular {@link AsyncTask#onPostExecute} is a bit hard to predict when it'll be called and
36 *   when it won't.
37 *
38 * Note this class is missing some of the {@link AsyncTask} features, e.g. it lacks
39 * {@link AsyncTask#onProgressUpdate}.  Add these when necessary.
40 */
41public abstract class EmailAsyncTask<Params, Progress, Result> {
42    private static final Executor SERIAL_EXECUTOR = AsyncTask.SERIAL_EXECUTOR;
43    private static final Executor PARALLEL_EXECUTOR = AsyncTask.THREAD_POOL_EXECUTOR;
44
45    /**
46     * Tracks {@link EmailAsyncTask}.
47     *
48     * Call {@link #cancelAllInterrupt()} to cancel all tasks registered.
49     */
50    public static class Tracker {
51        private final LinkedList<EmailAsyncTask<?, ?, ?>> mTasks =
52                new LinkedList<EmailAsyncTask<?, ?, ?>>();
53
54        private void add(EmailAsyncTask<?, ?, ?> task) {
55            synchronized (mTasks) {
56                mTasks.add(task);
57            }
58        }
59
60        private void remove(EmailAsyncTask<?, ?, ?> task) {
61            synchronized (mTasks) {
62                mTasks.remove(task);
63            }
64        }
65
66        /**
67         * Cancel all registered tasks.
68         */
69        @VisibleForTesting
70        public void cancelAllInterrupt() {
71            synchronized (mTasks) {
72                for (EmailAsyncTask<?, ?, ?> task : mTasks) {
73                    task.cancel(true);
74                }
75                mTasks.clear();
76            }
77        }
78
79        /**
80         * Cancel all instances of the same class as {@code current} other than
81         * {@code current} itself.
82         */
83        /* package */ void cancelOthers(EmailAsyncTask<?, ?, ?> current) {
84            final Class<?> clazz = current.getClass();
85            synchronized (mTasks) {
86                final ArrayList<EmailAsyncTask<?, ?, ?>> toRemove =
87                        new ArrayList<EmailAsyncTask<?, ?, ?>>();
88                for (EmailAsyncTask<?, ?, ?> task : mTasks) {
89                    if ((task != current) && task.getClass().equals(clazz)) {
90                        task.cancel(true);
91                        toRemove.add(task);
92                    }
93                }
94                for (EmailAsyncTask<?, ?, ?> task : toRemove) {
95                    mTasks.remove(task);
96                }
97            }
98        }
99
100        /* package */ int getTaskCountForTest() {
101            return mTasks.size();
102        }
103
104        /* package */ boolean containsTaskForTest(EmailAsyncTask<?, ?, ?> task) {
105            return mTasks.contains(task);
106        }
107    }
108
109    private final Tracker mTracker;
110
111    private static class InnerTask<Params2, Progress2, Result2>
112            extends AsyncTask<Params2, Progress2, Result2> {
113        private final EmailAsyncTask<Params2, Progress2, Result2> mOwner;
114
115        public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) {
116            mOwner = owner;
117        }
118
119        @Override
120        protected Result2 doInBackground(Params2... params) {
121            return mOwner.doInBackground(params);
122        }
123
124        @Override
125        public void onCancelled(Result2 result) {
126            mOwner.unregisterSelf();
127            mOwner.onCancelled(result);
128        }
129
130        @Override
131        public void onPostExecute(Result2 result) {
132            mOwner.unregisterSelf();
133            if (mOwner.mCancelled) {
134                mOwner.onCancelled(result);
135            } else {
136                mOwner.onSuccess(result);
137            }
138        }
139    }
140
141    private final InnerTask<Params, Progress, Result> mInnerTask;
142    private volatile boolean mCancelled;
143
144    public EmailAsyncTask(Tracker tracker) {
145        mTracker = tracker;
146        if (mTracker != null) {
147            mTracker.add(this);
148        }
149        mInnerTask = new InnerTask<Params, Progress, Result>(this);
150    }
151
152    /* package */ final void unregisterSelf() {
153        if (mTracker != null) {
154            mTracker.remove(this);
155        }
156    }
157
158    /** @see AsyncTask#doInBackground */
159    protected abstract Result doInBackground(Params... params);
160
161
162    /** @see AsyncTask#cancel(boolean) */
163    public final void cancel(boolean mayInterruptIfRunning) {
164        mCancelled = true;
165        mInnerTask.cancel(mayInterruptIfRunning);
166    }
167
168    /** @see AsyncTask#onCancelled */
169    protected void onCancelled(Result result) {
170    }
171
172    /**
173     * Similar to {@link AsyncTask#onPostExecute}, but this will never be executed if
174     * {@link #cancel(boolean)} has been called before its execution, even if
175     * {@link #doInBackground(Object...)} has completed when cancelled.
176     *
177     * @see AsyncTask#onPostExecute
178     */
179    protected void onSuccess(Result result) {
180    }
181
182    /**
183     * execute on {@link #PARALLEL_EXECUTOR}
184     *
185     * @see AsyncTask#execute
186     */
187    public final EmailAsyncTask<Params, Progress, Result> executeParallel(Params... params) {
188        return executeInternal(PARALLEL_EXECUTOR, false, params);
189    }
190
191    /**
192     * execute on {@link #SERIAL_EXECUTOR}
193     *
194     * @see AsyncTask#execute
195     */
196    public final EmailAsyncTask<Params, Progress, Result> executeSerial(Params... params) {
197        return executeInternal(SERIAL_EXECUTOR, false, params);
198    }
199
200    /**
201     * Cancel all previously created instances of the same class tracked by the same
202     * {@link Tracker}, and then {@link #executeParallel}.
203     */
204    public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteParallel(
205            Params... params) {
206        return executeInternal(PARALLEL_EXECUTOR, true, params);
207    }
208
209    /**
210     * Cancel all previously created instances of the same class tracked by the same
211     * {@link Tracker}, and then {@link #executeSerial}.
212     */
213    public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteSerial(
214            Params... params) {
215        return executeInternal(SERIAL_EXECUTOR, true, params);
216    }
217
218    private EmailAsyncTask<Params, Progress, Result> executeInternal(Executor executor,
219            boolean cancelPrevious, Params... params) {
220        if (cancelPrevious) {
221            if (mTracker == null) {
222                throw new IllegalStateException();
223            } else {
224                mTracker.cancelOthers(this);
225            }
226        }
227        mInnerTask.executeOnExecutor(executor, params);
228        return this;
229    }
230
231    /**
232     * Runs a {@link Runnable} in a bg thread, using {@link #PARALLEL_EXECUTOR}.
233     */
234    public static EmailAsyncTask<Void, Void, Void> runAsyncParallel(Runnable runnable) {
235        return runAsyncInternal(PARALLEL_EXECUTOR, runnable);
236    }
237
238    /**
239     * Runs a {@link Runnable} in a bg thread, using {@link #SERIAL_EXECUTOR}.
240     */
241    public static EmailAsyncTask<Void, Void, Void> runAsyncSerial(Runnable runnable) {
242        return runAsyncInternal(SERIAL_EXECUTOR, runnable);
243    }
244
245    private static EmailAsyncTask<Void, Void, Void> runAsyncInternal(Executor executor,
246            final Runnable runnable) {
247        EmailAsyncTask<Void, Void, Void> task = new EmailAsyncTask<Void, Void, Void>(null) {
248            @Override
249            protected Void doInBackground(Void... params) {
250                runnable.run();
251                return null;
252            }
253        };
254        return task.executeInternal(executor, false, (Void[]) null);
255    }
256
257    /**
258     * Wait until {@link #doInBackground} finishes and returns the results of the computation.
259     *
260     * @see AsyncTask#get
261     */
262    public final Result get() throws InterruptedException, ExecutionException {
263        return mInnerTask.get();
264    }
265
266    /* package */ final Result callDoInBackgroundForTest(Params... params) {
267        return mInnerTask.doInBackground(params);
268    }
269
270    /* package */ final void callOnCancelledForTest(Result result) {
271        mInnerTask.onCancelled(result);
272    }
273
274    /* package */ final void callOnPostExecuteForTest(Result result) {
275        mInnerTask.onPostExecute(result);
276    }
277}
278