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