1 package org.robolectric.shadows; 2 3 import android.os.AsyncTask; 4 import java.util.concurrent.Callable; 5 import java.util.concurrent.CancellationException; 6 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.Executor; 8 import java.util.concurrent.FutureTask; 9 import java.util.concurrent.TimeUnit; 10 import java.util.concurrent.TimeoutException; 11 import org.robolectric.annotation.Implementation; 12 import org.robolectric.annotation.Implements; 13 import org.robolectric.annotation.RealObject; 14 15 @Implements(AsyncTask.class) 16 public class ShadowAsyncTask<Params, Progress, Result> { 17 18 @RealObject private AsyncTask<Params, Progress, Result> realAsyncTask; 19 20 private final FutureTask<Result> future; 21 private final BackgroundWorker worker; 22 private AsyncTask.Status status = AsyncTask.Status.PENDING; 23 24 public ShadowAsyncTask() { 25 worker = new BackgroundWorker(); 26 future = new FutureTask<Result>(worker) { 27 @Override 28 protected void done() { 29 status = AsyncTask.Status.FINISHED; 30 try { 31 final Result result = get(); 32 33 try { 34 ShadowApplication.getInstance().getForegroundThreadScheduler().post(new Runnable() { 35 @Override 36 public void run() { 37 getBridge().onPostExecute(result); 38 } 39 }); 40 } catch (Throwable t) { 41 throw new OnPostExecuteException(t); 42 } 43 } catch (CancellationException e) { 44 ShadowApplication.getInstance().getForegroundThreadScheduler().post(new Runnable() { 45 @Override 46 public void run() { 47 getBridge().onCancelled(); 48 } 49 }); 50 } catch (InterruptedException e) { 51 // Ignore. 52 } catch (OnPostExecuteException e) { 53 throw new RuntimeException(e.getCause()); 54 } catch (Throwable t) { 55 throw new RuntimeException("An error occured while executing doInBackground()", 56 t.getCause()); 57 } 58 } 59 }; 60 } 61 62 @Implementation 63 public boolean isCancelled() { 64 return future.isCancelled(); 65 } 66 67 @Implementation 68 public boolean cancel(boolean mayInterruptIfRunning) { 69 return future.cancel(mayInterruptIfRunning); 70 } 71 72 @Implementation 73 public Result get() throws InterruptedException, ExecutionException { 74 return future.get(); 75 } 76 77 @Implementation 78 public Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 79 return future.get(timeout, unit); 80 } 81 82 @Implementation 83 public AsyncTask<Params, Progress, Result> execute(final Params... params) { 84 status = AsyncTask.Status.RUNNING; 85 getBridge().onPreExecute(); 86 87 worker.params = params; 88 89 ShadowApplication.getInstance().getBackgroundThreadScheduler().post(new Runnable() { 90 @Override 91 public void run() { 92 future.run(); 93 } 94 }); 95 96 return realAsyncTask; 97 } 98 99 @Implementation 100 public AsyncTask<Params, Progress, Result> executeOnExecutor(Executor executor, Params... params) { 101 status = AsyncTask.Status.RUNNING; 102 getBridge().onPreExecute(); 103 104 worker.params = params; 105 executor.execute(new Runnable() { 106 @Override 107 public void run() { 108 future.run(); 109 } 110 }); 111 112 return realAsyncTask; 113 } 114 115 @Implementation 116 public AsyncTask.Status getStatus() { 117 return status; 118 } 119 120 /** 121 * Enqueue a call to {@link AsyncTask#onProgressUpdate(Object[])} on UI looper (or run it immediately 122 * if the looper it is not paused). 123 * 124 * @param values The progress values to update the UI with. 125 * @see AsyncTask#publishProgress(Object[]) 126 */ 127 @Implementation 128 public void publishProgress(final Progress... values) { 129 ShadowApplication.getInstance().getForegroundThreadScheduler().post(new Runnable() { 130 @Override 131 public void run() { 132 getBridge().onProgressUpdate(values); 133 } 134 }); 135 } 136 137 private ShadowAsyncTaskBridge<Params, Progress, Result> getBridge() { 138 return new ShadowAsyncTaskBridge<>(realAsyncTask); 139 } 140 141 private final class BackgroundWorker implements Callable<Result> { 142 Params[] params; 143 144 @Override 145 public Result call() throws Exception { 146 return getBridge().doInBackground(params); 147 } 148 } 149 150 private static class OnPostExecuteException extends Exception { 151 public OnPostExecuteException(Throwable throwable) { 152 super(throwable); 153 } 154 } 155 } 156