content: /*
   * Copyright (C) 2014 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.hardware.camera2.utils;
  
  import android.util.Log;
  
  import java.util.HashSet;
  import java.util.Set;
  import java.util.concurrent.Executor;
  
  import static com.android.internal.util.Preconditions.*;
  
  /**
   * Keep track of multiple concurrent tasks starting and finishing by their key;
   * allow draining existing tasks and figuring out when all tasks have finished
   * (and new ones won't begin).
   *
   * <p>The initial state is to allow all tasks to be started and finished. A task may only be started
   * once, after which it must be finished before starting again. Likewise, a task may only be
   * finished once, after which it must be started before finishing again. It is okay to finish a
   * task before starting it due to different threads handling starting and finishing.</p>
   *
   * <p>When draining begins, no more new tasks can be started. This guarantees that at some
   * point when all the tasks are finished there will be no more collective new tasks,
   * at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
   *
   *
   * @param <T>
   *          a type for the key that will represent tracked tasks;
   *          must implement {@code Object#equals}
   */
  public class TaskDrainer<T> {
      /**
       * Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
       * <em>and</em> all tasks that were started have finished.
       */
      public interface DrainListener {
          /** All tasks have fully finished draining; there will be no more pending tasks. */
          public void onDrained();
      }
  
      private static final String TAG = "TaskDrainer";
      private final boolean DEBUG = false;
  
      private final Executor mExecutor;
      private final DrainListener mListener;
      private final String mName;
  
      /** Set of tasks which have been started but not yet finished with #taskFinished */
      private final Set<T> mTaskSet = new HashSet<T>();
      /**
       * Set of tasks which have been finished but not yet started with #taskStarted. This may happen
       * if taskStarted and taskFinished are called from two different threads.
       */
      private final Set<T> mEarlyFinishedTaskSet = new HashSet<T>();
      private final Object mLock = new Object();
  
      private boolean mDraining = false;
      private boolean mDrainFinished = false;
  
      /**
       * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
       * via the {@code executor}.
       *
       * @param executor a non-{@code null} executor to use for listener execution
       * @param listener a non-{@code null} listener where {@code onDrained} will be called
       */
      public TaskDrainer(Executor executor, DrainListener listener) {
          mExecutor = checkNotNull(executor, "executor must not be null");
          mListener = checkNotNull(listener, "listener must not be null");
          mName = null;
      }
  
      /**
       * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
       * via the {@code executor}.
       *
       * @param executor a non-{@code null} executor to use for listener execution
       * @param listener a non-{@code null} listener where {@code onDrained} will be called
       * @param name an optional name used for debug logging
       */
      public TaskDrainer(Executor executor, DrainListener listener, String name) {
          mExecutor = checkNotNull(executor, "executor must not be null");
          mListener = checkNotNull(listener, "listener must not be null");
          mName = name;
      }
  
      /**
       * Mark an asynchronous task as having started.
       *
       * <p>A task cannot be started more than once without first having finished. Once
       * draining begins with {@link #beginDrain}, no new tasks can be started.</p>
       *
       * @param task a key to identify a task
       *
       * @see #taskFinished
       * @see #beginDrain
       *
       * @throws IllegalStateException
       *          If attempting to start a task which is already started (and not finished),
       *          or if attempting to start a task after draining has begun.
       */
      public void taskStarted(T task) {
          synchronized (mLock) {
              if (DEBUG) {
                  Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
              }
  
              if (mDraining) {
                  throw new IllegalStateException("Can't start more tasks after draining has begun");
              }
  
              // Try to remove the task from the early finished set.
              if (!mEarlyFinishedTaskSet.remove(task)) {
                  // The task is not finished early. Add it to the started set.
                  if (!mTaskSet.add(task)) {
                      throw new IllegalStateException("Task " + task + " was already started");
                  }
              }
          }
      }
  
  
      /**
       * Mark an asynchronous task as having finished.
       *
       * <p>A task cannot be finished more than once without first having started.</p>
       *
       * @param task a key to identify a task
       *
       * @see #taskStarted
       * @see #beginDrain
       *
       * @throws IllegalStateException
       *          If attempting to finish a task which is already finished (and not started),
       */
      public void taskFinished(T task) {
          synchronized (mLock) {
              if (DEBUG) {
                  Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
              }
  
              // Try to remove the task from started set.
              if (!mTaskSet.remove(task)) {
                  // Task is not started yet. Add it to the early finished set.
                  if (!mEarlyFinishedTaskSet.add(task)) {
                      throw new IllegalStateException("Task " + task + " was already finished");
                  }
              }
  
              // If this is the last finished task and draining has already begun, fire #onDrained
              checkIfDrainFinished();
          }
      }
  
      /**
       * Do not allow any more tasks to be started; once all existing started tasks are finished,
       * fire the {@link DrainListener#onDrained} callback asynchronously.
       *
       * <p>This operation is idempotent; calling it more than once has no effect.</p>
       */
      public void beginDrain() {
          synchronized (mLock) {
              if (!mDraining) {
                  if (DEBUG) {
                      Log.v(TAG + "[" + mName + "]", "beginDrain started");
                  }
  
                  mDraining = true;
  
                  // If all tasks that had started had already finished by now, fire #onDrained
                  checkIfDrainFinished();
              } else {
                  if (DEBUG) {
                      Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
                  }
              }
          }
      }
  
      private void checkIfDrainFinished() {
          if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
              mDrainFinished = true;
              postDrained();
          }
      }
  
      private void postDrained() {
          mExecutor.execute(() -> {
                  if (DEBUG) {
                      Log.v(TAG + "[" + mName + "]", "onDrained");
                  }
  
                  mListener.onDrained();
          });
      }
  }
  