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.ConditionVariable;
21import android.util.Log;
22
23import java.lang.reflect.Constructor;
24import java.lang.reflect.InvocationTargetException;
25import java.util.concurrent.ScheduledThreadPoolExecutor;
26import java.util.concurrent.TimeUnit;
27
28/**
29 * @hide
30 */
31public class SyncRunner extends GraphRunner {
32
33    private Scheduler mScheduler = null;
34
35    private OnRunnerDoneListener mDoneListener = null;
36    private ScheduledThreadPoolExecutor mWakeExecutor = new ScheduledThreadPoolExecutor(1);
37    private ConditionVariable mWakeCondition = new ConditionVariable();
38
39    private StopWatchMap mTimer = null;
40
41    private final boolean mLogVerbose;
42    private final static String TAG = "SyncRunner";
43
44    // TODO: Provide factory based constructor?
45    public SyncRunner(FilterContext context, FilterGraph graph, Class schedulerClass) {
46        super(context);
47
48        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
49
50        if (mLogVerbose) Log.v(TAG, "Initializing SyncRunner");
51
52        // Create the scheduler
53        if (Scheduler.class.isAssignableFrom(schedulerClass)) {
54            try {
55                Constructor schedulerConstructor = schedulerClass.getConstructor(FilterGraph.class);
56                mScheduler = (Scheduler)schedulerConstructor.newInstance(graph);
57            } catch (NoSuchMethodException e) {
58                throw new RuntimeException("Scheduler does not have constructor <init>(FilterGraph)!", e);
59            } catch (InstantiationException e) {
60                throw new RuntimeException("Could not instantiate the Scheduler instance!", e);
61            } catch (IllegalAccessException e) {
62                throw new RuntimeException("Cannot access Scheduler constructor!", e);
63            } catch (InvocationTargetException e) {
64                throw new RuntimeException("Scheduler constructor threw an exception", e);
65            } catch (Exception e) {
66                throw new RuntimeException("Could not instantiate Scheduler", e);
67            }
68        } else {
69            throw new IllegalArgumentException("Class provided is not a Scheduler subclass!");
70        }
71
72        // Associate this runner and the graph with the context
73        mFilterContext = context;
74        mFilterContext.addGraph(graph);
75
76        mTimer = new StopWatchMap();
77
78        if (mLogVerbose) Log.v(TAG, "Setting up filters");
79
80        // Setup graph filters
81        graph.setupFilters();
82    }
83
84    @Override
85    public FilterGraph getGraph() {
86        return mScheduler != null ? mScheduler.getGraph() : null;
87    }
88
89    public int step() {
90        assertReadyToStep();
91        if (!getGraph().isReady() ) {
92            throw new RuntimeException("Trying to process graph that is not open!");
93        }
94        return performStep() ? RESULT_RUNNING : determinePostRunState();
95    }
96
97    public void beginProcessing() {
98        mScheduler.reset();
99        getGraph().beginProcessing();
100    }
101
102    public void close() {
103        // Close filters
104        if (mLogVerbose) Log.v(TAG, "Closing graph.");
105        getGraph().closeFilters(mFilterContext);
106        mScheduler.reset();
107    }
108
109    @Override
110    public void run() {
111        if (mLogVerbose) Log.v(TAG, "Beginning run.");
112
113        assertReadyToStep();
114
115        // Preparation
116        beginProcessing();
117        boolean glActivated = activateGlContext();
118
119        // Run
120        boolean keepRunning = true;
121        while (keepRunning) {
122            keepRunning = performStep();
123        }
124
125        // Cleanup
126        if (glActivated) {
127            deactivateGlContext();
128        }
129
130        // Call completion callback if set
131        if (mDoneListener != null) {
132            if (mLogVerbose) Log.v(TAG, "Calling completion listener.");
133            mDoneListener.onRunnerDone(determinePostRunState());
134        }
135        if (mLogVerbose) Log.v(TAG, "Run complete");
136    }
137
138    @Override
139    public boolean isRunning() {
140        return false;
141    }
142
143    @Override
144    public void setDoneCallback(OnRunnerDoneListener listener) {
145        mDoneListener = listener;
146    }
147
148    @Override
149    public void stop() {
150        throw new RuntimeException("SyncRunner does not support stopping a graph!");
151    }
152
153    @Override
154    synchronized public Exception getError() {
155        return null;
156    }
157
158    protected void waitUntilWake() {
159        mWakeCondition.block();
160    }
161
162    protected void processFilterNode(Filter filter) {
163        if (mLogVerbose) Log.v(TAG, "Processing filter node");
164        filter.performProcess(mFilterContext);
165        if (filter.getStatus() == Filter.STATUS_ERROR) {
166            throw new RuntimeException("There was an error executing " + filter + "!");
167        } else if (filter.getStatus() == Filter.STATUS_SLEEPING) {
168            if (mLogVerbose) Log.v(TAG, "Scheduling filter wakeup");
169            scheduleFilterWake(filter, filter.getSleepDelay());
170        }
171    }
172
173    protected void scheduleFilterWake(Filter filter, int delay) {
174        // Close the wake condition
175        mWakeCondition.close();
176
177        // Schedule the wake-up
178        final Filter filterToSchedule = filter;
179        final ConditionVariable conditionToWake = mWakeCondition;
180
181        mWakeExecutor.schedule(new Runnable() {
182          @Override
183          public void run() {
184                filterToSchedule.unsetStatus(Filter.STATUS_SLEEPING);
185                conditionToWake.open();
186            }
187        }, delay, TimeUnit.MILLISECONDS);
188    }
189
190    protected int determinePostRunState() {
191        boolean isBlocked = false;
192        for (Filter filter : mScheduler.getGraph().getFilters()) {
193            if (filter.isOpen()) {
194                if (filter.getStatus() == Filter.STATUS_SLEEPING) {
195                    // If ANY node is sleeping, we return our state as sleeping
196                    return RESULT_SLEEPING;
197                } else {
198                    // If a node is still open, it is blocked (by input or output)
199                    return RESULT_BLOCKED;
200                }
201            }
202        }
203        return RESULT_FINISHED;
204    }
205
206    // Core internal methods ///////////////////////////////////////////////////////////////////////
207    boolean performStep() {
208        if (mLogVerbose) Log.v(TAG, "Performing one step.");
209        Filter filter = mScheduler.scheduleNextNode();
210        if (filter != null) {
211            mTimer.start(filter.getName());
212            processFilterNode(filter);
213            mTimer.stop(filter.getName());
214            return true;
215        } else {
216            return false;
217        }
218    }
219
220    void assertReadyToStep() {
221        if (mScheduler == null) {
222            throw new RuntimeException("Attempting to run schedule with no scheduler in place!");
223        } else if (getGraph() == null) {
224            throw new RuntimeException("Calling step on scheduler with no graph in place!");
225        }
226    }
227}
228