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 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