content: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  package android.filterfw.core;
  
  import android.os.AsyncTask;
  
  import android.util.Log;
  
  /**
   * @hide
   */
  public class AsyncRunner extends GraphRunner{
  
      private Class mSchedulerClass;
      private SyncRunner mRunner;
      private AsyncRunnerTask mRunTask;
  
      private OnRunnerDoneListener mDoneListener;
      private boolean isProcessing;
  
      private Exception mException;
  
      private class RunnerResult {
          public int status = RESULT_UNKNOWN;
          public Exception exception;
      }
  
      private class AsyncRunnerTask extends AsyncTask<SyncRunner, Void, RunnerResult> {
  
          private static final String TAG = "AsyncRunnerTask";
  
          @Override
          protected RunnerResult doInBackground(SyncRunner... runner) {
              RunnerResult result = new RunnerResult();
              try {
                  if (runner.length > 1) {
                      throw new RuntimeException("More than one runner received!");
                  }
  
                  runner[0].assertReadyToStep();
  
                  // Preparation
                  if (mLogVerbose) Log.v(TAG, "Starting background graph processing.");
                  activateGlContext();
  
                  if (mLogVerbose) Log.v(TAG, "Preparing filter graph for processing.");
                  runner[0].beginProcessing();
  
                  if (mLogVerbose) Log.v(TAG, "Running graph.");
  
                  // Run loop
                  result.status = RESULT_RUNNING;
                  while (!isCancelled() && result.status == RESULT_RUNNING) {
                      if (!runner[0].performStep()) {
                          result.status = runner[0].determinePostRunState();
                          if (result.status == GraphRunner.RESULT_SLEEPING) {
                              runner[0].waitUntilWake();
                              result.status = RESULT_RUNNING;
                          }
                      }
                  }
  
                  // Cleanup
                  if (isCancelled()) {
                      result.status = RESULT_STOPPED;
                  }
              } catch (Exception exception) {
                  result.exception = exception;
                  result.status = RESULT_ERROR;
              }
  
              // Deactivate context.
              try {
                  deactivateGlContext();
              } catch (Exception exception) {
                  result.exception = exception;
                  result.status = RESULT_ERROR;
              }
  
              if (mLogVerbose) Log.v(TAG, "Done with background graph processing.");
              return result;
          }
  
          @Override
          protected void onCancelled(RunnerResult result) {
              onPostExecute(result);
          }
  
          @Override
          protected void onPostExecute(RunnerResult result) {
              if (mLogVerbose) Log.v(TAG, "Starting post-execute.");
              setRunning(false);
              if (result == null) {
                  // Cancelled before got to doInBackground
                  result = new RunnerResult();
                  result.status = RESULT_STOPPED;
              }
              setException(result.exception);
              if (result.status == RESULT_STOPPED || result.status == RESULT_ERROR) {
                  if (mLogVerbose) Log.v(TAG, "Closing filters.");
                  try {
                      mRunner.close();
                  } catch (Exception exception) {
                      result.status = RESULT_ERROR;
                      setException(exception);
                  }
              }
              if (mDoneListener != null) {
                  if (mLogVerbose) Log.v(TAG, "Calling graph done callback.");
                  mDoneListener.onRunnerDone(result.status);
              }
              if (mLogVerbose) Log.v(TAG, "Completed post-execute.");
          }
      }
  
      private boolean mLogVerbose;
      private static final String TAG = "AsyncRunner";
  
      /** Create a new asynchronous graph runner with the given filter
       * context, and the given scheduler class.
       *
       * Must be created on the UI thread.
       */
      public AsyncRunner(FilterContext context, Class schedulerClass) {
          super(context);
  
          mSchedulerClass = schedulerClass;
          mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
      }
  
      /** Create a new asynchronous graph runner with the given filter
       * context. Uses a default scheduler.
       *
       * Must be created on the UI thread.
       */
      public AsyncRunner(FilterContext context) {
          super(context);
  
          mSchedulerClass = SimpleScheduler.class;
          mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
      }
  
      /** Set a callback to be called in the UI thread once the AsyncRunner
       * completes running a graph, whether the completion is due to a stop() call
       * or the filters running out of data to process.
       */
      @Override
      public void setDoneCallback(OnRunnerDoneListener listener) {
          mDoneListener = listener;
      }
  
      /** Sets the graph to be run. Will call prepare() on graph. Cannot be called
       * when a graph is already running.
       */
      synchronized public void setGraph(FilterGraph graph) {
          if (isRunning()) {
              throw new RuntimeException("Graph is already running!");
          }
          mRunner = new SyncRunner(mFilterContext, graph, mSchedulerClass);
      }
  
      @Override
      public FilterGraph getGraph() {
          return mRunner != null ? mRunner.getGraph() : null;
      }
  
      /** Execute the graph in a background thread. */
      @Override
      synchronized public void run() {
          if (mLogVerbose) Log.v(TAG, "Running graph.");
          setException(null);
  
          if (isRunning()) {
              throw new RuntimeException("Graph is already running!");
          }
          if (mRunner == null) {
              throw new RuntimeException("Cannot run before a graph is set!");
          }
          mRunTask = this.new AsyncRunnerTask();
  
          setRunning(true);
          mRunTask.execute(mRunner);
      }
  
      /** Stop graph execution. This is an asynchronous call; register a callback
       * with setDoneCallback to be notified of when the background processing has
       * been completed. Calling stop will close the filter graph. */
      @Override
      synchronized public void stop() {
          if (mRunTask != null && !mRunTask.isCancelled() ) {
              if (mLogVerbose) Log.v(TAG, "Stopping graph.");
              mRunTask.cancel(false);
          }
      }
  
      @Override
      synchronized public void close() {
          if (isRunning()) {
              throw new RuntimeException("Cannot close graph while it is running!");
          }
          if (mLogVerbose) Log.v(TAG, "Closing filters.");
          mRunner.close();
      }
  
      /** Check if background processing is happening */
      @Override
      synchronized public boolean isRunning() {
          return isProcessing;
      }
  
      @Override
      synchronized public Exception getError() {
          return mException;
      }
  
      synchronized private void setRunning(boolean running) {
          isProcessing = running;
      }
  
      synchronized private void setException(Exception exception) {
          mException = exception;
      }
  
  }
  