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
18package android.filterfw.core;
19
20import android.os.AsyncTask;
21import android.os.Handler;
22
23import android.util.Log;
24
25import java.lang.InterruptedException;
26import java.lang.Runnable;
27import java.util.concurrent.CancellationException;
28import java.util.concurrent.ExecutionException;
29import java.util.concurrent.TimeoutException;
30import java.util.concurrent.TimeUnit;
31
32/**
33 * @hide
34 */
35public class AsyncRunner extends GraphRunner{
36
37    private Class mSchedulerClass;
38    private SyncRunner mRunner;
39    private AsyncRunnerTask mRunTask;
40
41    private OnRunnerDoneListener mDoneListener;
42    private boolean isProcessing;
43
44    private Exception mException;
45
46    private class RunnerResult {
47        public int status = RESULT_UNKNOWN;
48        public Exception exception;
49    }
50
51    private class AsyncRunnerTask extends AsyncTask<SyncRunner, Void, RunnerResult> {
52
53        private static final String TAG = "AsyncRunnerTask";
54
55        @Override
56        protected RunnerResult doInBackground(SyncRunner... runner) {
57            RunnerResult result = new RunnerResult();
58            try {
59                if (runner.length > 1) {
60                    throw new RuntimeException("More than one runner received!");
61                }
62
63                runner[0].assertReadyToStep();
64
65                // Preparation
66                if (mLogVerbose) Log.v(TAG, "Starting background graph processing.");
67                activateGlContext();
68
69                if (mLogVerbose) Log.v(TAG, "Preparing filter graph for processing.");
70                runner[0].beginProcessing();
71
72                if (mLogVerbose) Log.v(TAG, "Running graph.");
73
74                // Run loop
75                result.status = RESULT_RUNNING;
76                while (!isCancelled() && result.status == RESULT_RUNNING) {
77                    if (!runner[0].performStep()) {
78                        result.status = runner[0].determinePostRunState();
79                        if (result.status == GraphRunner.RESULT_SLEEPING) {
80                            runner[0].waitUntilWake();
81                            result.status = RESULT_RUNNING;
82                        }
83                    }
84                }
85
86                // Cleanup
87                if (isCancelled()) {
88                    result.status = RESULT_STOPPED;
89                }
90            } catch (Exception exception) {
91                result.exception = exception;
92                result.status = RESULT_ERROR;
93            }
94
95            // Deactivate context.
96            try {
97                deactivateGlContext();
98            } catch (Exception exception) {
99                result.exception = exception;
100                result.status = RESULT_ERROR;
101            }
102
103            if (mLogVerbose) Log.v(TAG, "Done with background graph processing.");
104            return result;
105        }
106
107        @Override
108        protected void onCancelled(RunnerResult result) {
109            onPostExecute(result);
110        }
111
112        @Override
113        protected void onPostExecute(RunnerResult result) {
114            if (mLogVerbose) Log.v(TAG, "Starting post-execute.");
115            setRunning(false);
116            if (result == null) {
117                // Cancelled before got to doInBackground
118                result = new RunnerResult();
119                result.status = RESULT_STOPPED;
120            }
121            setException(result.exception);
122            if (result.status == RESULT_STOPPED || result.status == RESULT_ERROR) {
123                if (mLogVerbose) Log.v(TAG, "Closing filters.");
124                try {
125                    mRunner.close();
126                } catch (Exception exception) {
127                    result.status = RESULT_ERROR;
128                    setException(exception);
129                }
130            }
131            if (mDoneListener != null) {
132                if (mLogVerbose) Log.v(TAG, "Calling graph done callback.");
133                mDoneListener.onRunnerDone(result.status);
134            }
135            if (mLogVerbose) Log.v(TAG, "Completed post-execute.");
136        }
137    }
138
139    private boolean mLogVerbose;
140    private static final String TAG = "AsyncRunner";
141
142    /** Create a new asynchronous graph runner with the given filter
143     * context, and the given scheduler class.
144     *
145     * Must be created on the UI thread.
146     */
147    public AsyncRunner(FilterContext context, Class schedulerClass) {
148        super(context);
149
150        mSchedulerClass = schedulerClass;
151        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
152    }
153
154    /** Create a new asynchronous graph runner with the given filter
155     * context. Uses a default scheduler.
156     *
157     * Must be created on the UI thread.
158     */
159    public AsyncRunner(FilterContext context) {
160        super(context);
161
162        mSchedulerClass = SimpleScheduler.class;
163        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
164    }
165
166    /** Set a callback to be called in the UI thread once the AsyncRunner
167     * completes running a graph, whether the completion is due to a stop() call
168     * or the filters running out of data to process.
169     */
170    @Override
171    public void setDoneCallback(OnRunnerDoneListener listener) {
172        mDoneListener = listener;
173    }
174
175    /** Sets the graph to be run. Will call prepare() on graph. Cannot be called
176     * when a graph is already running.
177     */
178    synchronized public void setGraph(FilterGraph graph) {
179        if (isRunning()) {
180            throw new RuntimeException("Graph is already running!");
181        }
182        mRunner = new SyncRunner(mFilterContext, graph, mSchedulerClass);
183    }
184
185    @Override
186    public FilterGraph getGraph() {
187        return mRunner != null ? mRunner.getGraph() : null;
188    }
189
190    /** Execute the graph in a background thread. */
191    @Override
192    synchronized public void run() {
193        if (mLogVerbose) Log.v(TAG, "Running graph.");
194        setException(null);
195
196        if (isRunning()) {
197            throw new RuntimeException("Graph is already running!");
198        }
199        if (mRunner == null) {
200            throw new RuntimeException("Cannot run before a graph is set!");
201        }
202        mRunTask = this.new AsyncRunnerTask();
203
204        setRunning(true);
205        mRunTask.execute(mRunner);
206    }
207
208    /** Stop graph execution. This is an asynchronous call; register a callback
209     * with setDoneCallback to be notified of when the background processing has
210     * been completed. Calling stop will close the filter graph. */
211    @Override
212    synchronized public void stop() {
213        if (mRunTask != null && !mRunTask.isCancelled() ) {
214            if (mLogVerbose) Log.v(TAG, "Stopping graph.");
215            mRunTask.cancel(false);
216        }
217    }
218
219    @Override
220    synchronized public void close() {
221        if (isRunning()) {
222            throw new RuntimeException("Cannot close graph while it is running!");
223        }
224        if (mLogVerbose) Log.v(TAG, "Closing filters.");
225        mRunner.close();
226    }
227
228    /** Check if background processing is happening */
229    @Override
230    synchronized public boolean isRunning() {
231        return isProcessing;
232    }
233
234    @Override
235    synchronized public Exception getError() {
236        return mException;
237    }
238
239    synchronized private void setRunning(boolean running) {
240        isProcessing = running;
241    }
242
243    synchronized private void setException(Exception exception) {
244        mException = exception;
245    }
246
247}
248