• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.emailcommon.utility;
18 
19 import android.os.AsyncTask;
20 
21 import java.util.ArrayList;
22 import java.util.LinkedList;
23 import java.util.concurrent.ExecutionException;
24 import 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  */
39 public 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 
add(EmailAsyncTask<?, ?, ?> task)52         private void add(EmailAsyncTask<?, ?, ?> task) {
53             synchronized (mTasks) {
54                 mTasks.add(task);
55             }
56         }
57 
remove(EmailAsyncTask<?, ?, ?> task)58         private void remove(EmailAsyncTask<?, ?, ?> task) {
59             synchronized (mTasks) {
60                 mTasks.remove(task);
61             }
62         }
63 
64         /**
65          * Cancel all registered tasks.
66          */
cancellAllInterrupt()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          */
cancelOthers(EmailAsyncTask<?, ?, ?> current)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 
getTaskCountForTest()97         /* package */ int getTaskCountForTest() {
98             return mTasks.size();
99         }
100 
containsTaskForTest(EmailAsyncTask<?, ?, ?> task)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 
InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner)112         public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) {
113             mOwner = owner;
114         }
115 
116         @Override
doInBackground(Params2... params)117         protected Result2 doInBackground(Params2... params) {
118             return mOwner.doInBackground(params);
119         }
120 
121         @Override
onCancelled(Result2 result)122         public void onCancelled(Result2 result) {
123             mOwner.unregisterSelf();
124             mOwner.onCancelled(result);
125         }
126 
127         @Override
onPostExecute(Result2 result)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 
EmailAsyncTask(Tracker tracker)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 
unregisterSelf()149     /* package */ final void unregisterSelf() {
150         if (mTracker != null) {
151             mTracker.remove(this);
152         }
153     }
154 
155     /** @see AsyncTask#doInBackground */
doInBackground(Params... params)156     protected abstract Result doInBackground(Params... params);
157 
158 
159     /** @see AsyncTask#cancel(boolean) */
cancel(boolean mayInterruptIfRunning)160     public final void cancel(boolean mayInterruptIfRunning) {
161         mCancelled = true;
162         mInnerTask.cancel(mayInterruptIfRunning);
163     }
164 
165     /** @see AsyncTask#onCancelled */
onCancelled(Result result)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      */
onSuccess(Result result)176     protected void onSuccess(Result result) {
177     }
178 
179     /**
180      * execute on {@link #PARALLEL_EXECUTOR}
181      *
182      * @see AsyncTask#execute
183      */
executeParallel(Params... params)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      */
executeSerial(Params... params)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      */
cancelPreviousAndExecuteParallel( Params... params)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      */
cancelPreviousAndExecuteSerial( Params... params)210     public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteSerial(
211             Params... params) {
212         return executeInternal(SERIAL_EXECUTOR, true, params);
213     }
214 
executeInternal(Executor executor, boolean cancelPrevious, Params... params)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      */
runAsyncParallel(Runnable runnable)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      */
runAsyncSerial(Runnable runnable)238     public static EmailAsyncTask<Void, Void, Void> runAsyncSerial(Runnable runnable) {
239         return runAsyncInternal(SERIAL_EXECUTOR, runnable);
240     }
241 
runAsyncInternal(Executor executor, final Runnable runnable)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      */
get()259     public final Result get() throws InterruptedException, ExecutionException {
260         return mInnerTask.get();
261     }
262 
callDoInBackgroundForTest(Params... params)263     /* package */ final Result callDoInBackgroundForTest(Params... params) {
264         return mInnerTask.doInBackground(params);
265     }
266 
callOnCancelledForTest(Result result)267     /* package */ final void callOnCancelledForTest(Result result) {
268         mInnerTask.onCancelled(result);
269     }
270 
callOnPostExecuteForTest(Result result)271     /* package */ final void callOnPostExecuteForTest(Result result) {
272         mInnerTask.onPostExecute(result);
273     }
274 }
275