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 266 @VisibleForTesting callDoInBackgroundForTest(Params... params)267 /* package */ final Result callDoInBackgroundForTest(Params... params) { 268 return mInnerTask.doInBackground(params); 269 } 270 271 @VisibleForTesting callOnCancelledForTest(Result result)272 /* package */ final void callOnCancelledForTest(Result result) { 273 mInnerTask.onCancelled(result); 274 } 275 276 @VisibleForTesting callOnPostExecuteForTest(Result result)277 /* package */ final void callOnPostExecuteForTest(Result result) { 278 mInnerTask.onPostExecute(result); 279 } 280 } 281