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 com.google.common.annotations.VisibleForTesting;
22 
23 import java.util.ArrayList;
24 import java.util.LinkedList;
25 import java.util.concurrent.ExecutionException;
26 import 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  */
41 public 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 
add(EmailAsyncTask<?, ?, ?> task)54         private void add(EmailAsyncTask<?, ?, ?> task) {
55             synchronized (mTasks) {
56                 mTasks.add(task);
57             }
58         }
59 
remove(EmailAsyncTask<?, ?, ?> task)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
cancelAllInterrupt()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          */
cancelOthers(EmailAsyncTask<?, ?, ?> current)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 
getTaskCountForTest()100         /* package */ int getTaskCountForTest() {
101             return mTasks.size();
102         }
103 
containsTaskForTest(EmailAsyncTask<?, ?, ?> task)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 
InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner)115         public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) {
116             mOwner = owner;
117         }
118 
119         @Override
doInBackground(Params2... params)120         protected Result2 doInBackground(Params2... params) {
121             return mOwner.doInBackground(params);
122         }
123 
124         @Override
onCancelled(Result2 result)125         public void onCancelled(Result2 result) {
126             mOwner.unregisterSelf();
127             mOwner.onCancelled(result);
128         }
129 
130         @Override
onPostExecute(Result2 result)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 
EmailAsyncTask(Tracker tracker)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 
unregisterSelf()152     /* package */ final void unregisterSelf() {
153         if (mTracker != null) {
154             mTracker.remove(this);
155         }
156     }
157 
158     /** @see AsyncTask#doInBackground */
doInBackground(Params... params)159     protected abstract Result doInBackground(Params... params);
160 
161 
162     /** @see AsyncTask#cancel(boolean) */
cancel(boolean mayInterruptIfRunning)163     public final void cancel(boolean mayInterruptIfRunning) {
164         mCancelled = true;
165         mInnerTask.cancel(mayInterruptIfRunning);
166     }
167 
168     /** @see AsyncTask#onCancelled */
onCancelled(Result result)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      */
onSuccess(Result result)179     protected void onSuccess(Result result) {
180     }
181 
182     /**
183      * execute on {@link #PARALLEL_EXECUTOR}
184      *
185      * @see AsyncTask#execute
186      */
executeParallel(Params... params)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      */
executeSerial(Params... params)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      */
cancelPreviousAndExecuteParallel( Params... params)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      */
cancelPreviousAndExecuteSerial( Params... params)213     public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteSerial(
214             Params... params) {
215         return executeInternal(SERIAL_EXECUTOR, true, params);
216     }
217 
executeInternal(Executor executor, boolean cancelPrevious, Params... params)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      */
runAsyncParallel(Runnable runnable)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      */
runAsyncSerial(Runnable runnable)241     public static EmailAsyncTask<Void, Void, Void> runAsyncSerial(Runnable runnable) {
242         return runAsyncInternal(SERIAL_EXECUTOR, runnable);
243     }
244 
runAsyncInternal(Executor executor, final Runnable runnable)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      */
get()262     public final Result get() throws InterruptedException, ExecutionException {
263         return mInnerTask.get();
264     }
265 
callDoInBackgroundForTest(Params... params)266     /* package */ final Result callDoInBackgroundForTest(Params... params) {
267         return mInnerTask.doInBackground(params);
268     }
269 
callOnCancelledForTest(Result result)270     /* package */ final void callOnCancelledForTest(Result result) {
271         mInnerTask.onCancelled(result);
272     }
273 
callOnPostExecuteForTest(Result result)274     /* package */ final void callOnPostExecuteForTest(Result result) {
275         mInnerTask.onPostExecute(result);
276     }
277 }
278