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