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