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.impl;
  
  import android.hardware.camera2.CameraAccessException;
  import android.hardware.camera2.CameraCaptureSession;
  import android.hardware.camera2.CameraDevice;
  import android.hardware.camera2.CaptureRequest;
  import android.hardware.camera2.ICameraDeviceUser;
  import android.hardware.camera2.params.OutputConfiguration;
  import android.hardware.camera2.utils.TaskDrainer;
  import android.hardware.camera2.utils.TaskSingleDrainer;
  import android.os.Binder;
  import android.os.Handler;
  import android.util.Log;
  import android.view.Surface;
  
  import java.util.Arrays;
  import java.util.List;
  import java.util.concurrent.Executor;
  
  import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler;
  import static com.android.internal.util.Preconditions.*;
  
  public class CameraCaptureSessionImpl extends CameraCaptureSession
          implements CameraCaptureSessionCore {
      private static final String TAG = "CameraCaptureSession";
      private static final boolean DEBUG = false;
  
      /** Simple integer ID for session for debugging */
      private final int mId;
      private final String mIdString;
  
      /** Input surface configured by native camera framework based on user-specified configuration */
      private final Surface mInput;
      /**
       * User-specified state callback, used for outgoing events; calls to this object will be
       * automatically invoked via {@code mStateExecutor}.
       */
      private final CameraCaptureSession.StateCallback mStateCallback;
      /** User-specified state executor used for outgoing state callback events */
      private final Executor mStateExecutor;
  
      /** Internal camera device; used to translate calls into existing deprecated API */
      private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl;
      /** Internal executor; used for all incoming events to preserve total order */
      private final Executor mDeviceExecutor;
  
      /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
      private final TaskDrainer<Integer> mSequenceDrainer;
      /** Drain state transitions from ACTIVE -> IDLE */
      private final TaskSingleDrainer mIdleDrainer;
      /** Drain state transitions from BUSY -> IDLE */
      private final TaskSingleDrainer mAbortDrainer;
  
      /** This session is closed; all further calls will throw ISE */
      private boolean mClosed = false;
      /** This session failed to be configured successfully */
      private final boolean mConfigureSuccess;
      /** Do not unconfigure if this is set; another session will overwrite configuration */
      private boolean mSkipUnconfigure = false;
  
      /** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */
      private volatile boolean mAborting;
  
      /**
       * Create a new CameraCaptureSession.
       *
       * <p>The camera device must already be in the {@code IDLE} state when this is invoked.
       * There must be no pending actions
       * (e.g. no pending captures, no repeating requests, no flush).</p>
       */
      CameraCaptureSessionImpl(int id, Surface input,
              CameraCaptureSession.StateCallback callback, Executor stateExecutor,
              android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
              Executor deviceStateExecutor, boolean configureSuccess) {
          if (callback == null) {
              throw new IllegalArgumentException("callback must not be null");
          }
  
          mId = id;
          mIdString = String.format("Session %d: ", mId);
  
          mInput = input;
          mStateExecutor = checkNotNull(stateExecutor, "stateExecutor must not be null");
          mStateCallback = createUserStateCallbackProxy(mStateExecutor, callback);
  
          mDeviceExecutor = checkNotNull(deviceStateExecutor,
                  "deviceStateExecutor must not be null");
          mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
  
          /*
           * Use the same handler as the device's StateCallback for all the internal coming events
           *
           * This ensures total ordering between CameraDevice.StateCallback and
           * CameraDeviceImpl.CaptureCallback events.
           */
          mSequenceDrainer = new TaskDrainer<>(mDeviceExecutor, new SequenceDrainListener(),
                  /*name*/"seq");
          mIdleDrainer = new TaskSingleDrainer(mDeviceExecutor, new IdleDrainListener(),
                  /*name*/"idle");
          mAbortDrainer = new TaskSingleDrainer(mDeviceExecutor, new AbortDrainListener(),
                  /*name*/"abort");
  
          // CameraDevice should call configureOutputs and have it finish before constructing us
  
          if (configureSuccess) {
              mStateCallback.onConfigured(this);
              if (DEBUG) Log.v(TAG, mIdString + "Created session successfully");
              mConfigureSuccess = true;
          } else {
              mStateCallback.onConfigureFailed(this);
              mClosed = true; // do not fire any other callbacks, do not allow any other work
              Log.e(TAG, mIdString + "Failed to create capture session; configuration failed");
              mConfigureSuccess = false;
          }
      }
  
      @Override
      public CameraDevice getDevice() {
          return mDeviceImpl;
      }
  
      @Override
      public void prepare(Surface surface) throws CameraAccessException {
          mDeviceImpl.prepare(surface);
      }
  
      @Override
      public void prepare(int maxCount, Surface surface) throws CameraAccessException {
          mDeviceImpl.prepare(maxCount, surface);
      }
  
      @Override
      public void tearDown(Surface surface) throws CameraAccessException {
          mDeviceImpl.tearDown(surface);
      }
  
      @Override
      public void finalizeOutputConfigurations(
              List<OutputConfiguration> outputConfigs) throws CameraAccessException {
          mDeviceImpl.finalizeOutputConfigs(outputConfigs);
      }
  
      @Override
      public int capture(CaptureRequest request, CaptureCallback callback,
              Handler handler) throws CameraAccessException {
          checkCaptureRequest(request);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              handler = checkHandler(handler, callback);
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +
                          " handler " + handler);
              }
  
              return addPendingSequence(mDeviceImpl.capture(request,
                      createCaptureCallbackProxy(handler, callback), mDeviceExecutor));
          }
      }
  
      @Override
      public int captureSingleRequest(CaptureRequest request, Executor executor,
              CaptureCallback callback) throws CameraAccessException {
          if (executor == null) {
              throw new IllegalArgumentException("executor must not be null");
          } else if (callback == null) {
              throw new IllegalArgumentException("callback must not be null");
          }
          checkCaptureRequest(request);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              executor = CameraDeviceImpl.checkExecutor(executor, callback);
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +
                          " executor " + executor);
              }
  
              return addPendingSequence(mDeviceImpl.capture(request,
                      createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor));
          }
      }
  
      private void checkCaptureRequest(CaptureRequest request) {
          if (request == null) {
              throw new IllegalArgumentException("request must not be null");
          } else if (request.isReprocess() && !isReprocessable()) {
              throw new IllegalArgumentException("this capture session cannot handle reprocess " +
                      "requests");
          } else if (request.isReprocess() && request.getReprocessableSessionId() != mId) {
              throw new IllegalArgumentException("capture request was created for another session");
          }
      }
  
      @Override
      public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
              Handler handler) throws CameraAccessException {
          checkCaptureRequests(requests);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              handler = checkHandler(handler, callback);
  
              if (DEBUG) {
                  CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
                  Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) +
                          ", callback " + callback + " handler " + handler);
              }
  
              return addPendingSequence(mDeviceImpl.captureBurst(requests,
                      createCaptureCallbackProxy(handler, callback), mDeviceExecutor));
          }
      }
  
      @Override
      public int captureBurstRequests(List<CaptureRequest> requests, Executor executor,
              CaptureCallback callback) throws CameraAccessException {
          if (executor == null) {
              throw new IllegalArgumentException("executor must not be null");
          } else if (callback == null) {
              throw new IllegalArgumentException("callback must not be null");
          }
          checkCaptureRequests(requests);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              executor = CameraDeviceImpl.checkExecutor(executor, callback);
  
              if (DEBUG) {
                  CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
                  Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) +
                          ", callback " + callback + " executor " + executor);
              }
  
              return addPendingSequence(mDeviceImpl.captureBurst(requests,
                      createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor));
          }
      }
  
      private void checkCaptureRequests(List<CaptureRequest> requests) {
          if (requests == null) {
              throw new IllegalArgumentException("Requests must not be null");
          } else if (requests.isEmpty()) {
              throw new IllegalArgumentException("Requests must have at least one element");
          }
  
          for (CaptureRequest request : requests) {
              if (request.isReprocess()) {
                  if (!isReprocessable()) {
                      throw new IllegalArgumentException("This capture session cannot handle " +
                              "reprocess requests");
                  } else if (request.getReprocessableSessionId() != mId) {
                      throw new IllegalArgumentException("Capture request was created for another " +
                              "session");
                  }
              }
          }
  
      }
  
      @Override
      public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
              Handler handler) throws CameraAccessException {
          checkRepeatingRequest(request);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              handler = checkHandler(handler, callback);
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " +
                          callback + " handler" + " " + handler);
              }
  
              return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
                      createCaptureCallbackProxy(handler, callback), mDeviceExecutor));
          }
      }
  
      @Override
      public int setSingleRepeatingRequest(CaptureRequest request, Executor executor,
              CaptureCallback callback) throws CameraAccessException {
          if (executor == null) {
              throw new IllegalArgumentException("executor must not be null");
          } else if (callback == null) {
              throw new IllegalArgumentException("callback must not be null");
          }
          checkRepeatingRequest(request);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              executor = CameraDeviceImpl.checkExecutor(executor, callback);
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " +
                          callback + " executor" + " " + executor);
              }
  
              return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
                      createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor));
          }
      }
  
      private void checkRepeatingRequest(CaptureRequest request) {
          if (request == null) {
              throw new IllegalArgumentException("request must not be null");
          } else if (request.isReprocess()) {
              throw new IllegalArgumentException("repeating reprocess requests are not supported");
          }
      }
  
      @Override
      public int setRepeatingBurst(List<CaptureRequest> requests,
              CaptureCallback callback, Handler handler) throws CameraAccessException {
          checkRepeatingRequests(requests);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              handler = checkHandler(handler, callback);
  
              if (DEBUG) {
                  CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
                  Log.v(TAG, mIdString + "setRepeatingBurst - requests " +
                          Arrays.toString(requestArray) + ", callback " + callback +
                          " handler" + "" + handler);
              }
  
              return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
                      createCaptureCallbackProxy(handler, callback), mDeviceExecutor));
          }
      }
  
      @Override
      public int setRepeatingBurstRequests(List<CaptureRequest> requests, Executor executor,
              CaptureCallback callback) throws CameraAccessException {
          if (executor == null) {
              throw new IllegalArgumentException("executor must not be null");
          } else if (callback == null) {
              throw new IllegalArgumentException("callback must not be null");
          }
          checkRepeatingRequests(requests);
  
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              executor = CameraDeviceImpl.checkExecutor(executor, callback);
  
              if (DEBUG) {
                  CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
                  Log.v(TAG, mIdString + "setRepeatingBurst - requests " +
                          Arrays.toString(requestArray) + ", callback " + callback +
                          " executor" + "" + executor);
              }
  
              return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
                      createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor));
          }
      }
  
      private void checkRepeatingRequests(List<CaptureRequest> requests) {
          if (requests == null) {
              throw new IllegalArgumentException("requests must not be null");
          } else if (requests.isEmpty()) {
              throw new IllegalArgumentException("requests must have at least one element");
          }
  
          for (CaptureRequest r : requests) {
              if (r.isReprocess()) {
                  throw new IllegalArgumentException("repeating reprocess burst requests are not " +
                          "supported");
              }
          }
      }
  
      @Override
      public void stopRepeating() throws CameraAccessException {
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "stopRepeating");
              }
  
              mDeviceImpl.stopRepeating();
          }
      }
  
      @Override
      public void abortCaptures() throws CameraAccessException {
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "abortCaptures");
              }
  
              if (mAborting) {
                  Log.w(TAG, mIdString + "abortCaptures - Session is already aborting; doing nothing");
                  return;
              }
  
              mAborting = true;
              mAbortDrainer.taskStarted();
  
              mDeviceImpl.flush();
              // The next BUSY -> IDLE set of transitions will mark the end of the abort.
          }
      }
  
      @Override
      public void updateOutputConfiguration(OutputConfiguration config)
              throws CameraAccessException {
          synchronized (mDeviceImpl.mInterfaceLock) {
              checkNotClosed();
  
              if (DEBUG) {
                  Log.v(TAG, mIdString + "updateOutputConfiguration");
              }
  
              mDeviceImpl.updateOutputConfiguration(config);
          }
      }
  
      @Override
      public boolean isReprocessable() {
          return mInput != null;
      }
  
      @Override
      public Surface getInputSurface() {
          return mInput;
      }
  
      /**
       * Replace this session with another session.
       *
       * <p>This is an optimization to avoid unconfiguring and then immediately having to
       * reconfigure again.</p>
       *
       * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
       * <p>
       *
       * <p>After this call completes, the session will not call any further methods on the camera
       * device.</p>
       *
       * @see CameraCaptureSession#close
       */
      @Override
      public void replaceSessionClose() {
          synchronized (mDeviceImpl.mInterfaceLock) {
              /*
               * In order for creating new sessions to be fast, the new session should be created
               * before the old session is closed.
               *
               * Otherwise the old session will always unconfigure if there is no new session to
               * replace it.
               *
               * Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
               * to skip unconfigure if a new session is created before the captures are all drained,
               * but this would introduce nondeterministic behavior.
               */
  
              if (DEBUG) Log.v(TAG, mIdString + "replaceSessionClose");
  
              // Set up fast shutdown. Possible alternative paths:
              // - This session is active, so close() below starts the shutdown drain
              // - This session is mid-shutdown drain, and hasn't yet reached the idle drain listener.
              // - This session is already closed and has executed the idle drain listener, and
              //   configureOutputsChecked(null) has already been called.
              //
              // Do not call configureOutputsChecked(null) going forward, since it would race with the
              // configuration for the new session. If it was already called, then we don't care,
              // since it won't get called again.
              mSkipUnconfigure = true;
              close();
          }
      }
  
      @Override
      public void close() {
          synchronized (mDeviceImpl.mInterfaceLock) {
              if (mClosed) {
                  if (DEBUG) Log.v(TAG, mIdString + "close - reentering");
                  return;
              }
  
              if (DEBUG) Log.v(TAG, mIdString + "close - first time");
  
              mClosed = true;
  
              /*
               * Flush out any repeating request. Since camera is closed, no new requests
               * can be queued, and eventually the entire request queue will be drained.
               *
               * If the camera device was already closed, short circuit and do nothing; since
               * no more internal device callbacks will fire anyway.
               *
               * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure
               * the camera. Once that's done, fire #onClosed.
               */
              try {
                  mDeviceImpl.stopRepeating();
              } catch (IllegalStateException e) {
                  // OK: Camera device may already be closed, nothing else to do
  
                  // TODO: Fire onClosed anytime we get the device onClosed or the ISE?
                  // or just suppress the ISE only and rely onClosed.
                  // Also skip any of the draining work if this is already closed.
  
                  // Short-circuit; queue callback immediately and return
                  mStateCallback.onClosed(this);
                  return;
              } catch (CameraAccessException e) {
                  // OK: close does not throw checked exceptions.
                  Log.e(TAG, mIdString + "Exception while stopping repeating: ", e);
  
                  // TODO: call onError instead of onClosed if this happens
              }
  
              // If no sequences are pending, fire #onClosed immediately
              mSequenceDrainer.beginDrain();
          }
          if (mInput != null) {
              mInput.release();
          }
      }
  
      /**
       * Whether currently in mid-abort.
       *
       * <p>This is used by the implementation to set the capture failure
       * reason, in lieu of more accurate error codes from the camera service.
       * Unsynchronized to avoid deadlocks between simultaneous session->device,
       * device->session calls.</p>
       *
       */
      @Override
      public boolean isAborting() {
          return mAborting;
      }
  
      /**
       * Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code executor}.
       */
      private StateCallback createUserStateCallbackProxy(Executor executor, StateCallback callback) {
          return new CallbackProxies.SessionStateCallbackProxy(executor, callback);
      }
  
      /**
       * Forward callbacks from
       * CameraDeviceImpl.CaptureCallback to the CameraCaptureSession.CaptureCallback.
       *
       * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
       */
      @SuppressWarnings("deprecation")
      private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxy(
              Handler handler, CaptureCallback callback) {
          final Executor executor = (callback != null) ? CameraDeviceImpl.checkAndWrapHandler(
                  handler) : null;
  
          return createCaptureCallbackProxyWithExecutor(executor, callback);
      }
  
      private CameraDeviceImpl.CaptureCallback createCaptureCallbackProxyWithExecutor(
              Executor executor, CaptureCallback callback) {
          return new CameraDeviceImpl.CaptureCallback() {
              @Override
              public void onCaptureStarted(CameraDevice camera,
                      CaptureRequest request, long timestamp, long frameNumber) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureStarted(
                                      CameraCaptureSessionImpl.this, request, timestamp,
                                      frameNumber));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
  
              @Override
              public void onCapturePartial(CameraDevice camera,
                      CaptureRequest request, android.hardware.camera2.CaptureResult result) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCapturePartial(
                                      CameraCaptureSessionImpl.this, request, result));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
  
              @Override
              public void onCaptureProgressed(CameraDevice camera,
                      CaptureRequest request, android.hardware.camera2.CaptureResult partialResult) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureProgressed(
                                      CameraCaptureSessionImpl.this, request, partialResult));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
  
              @Override
              public void onCaptureCompleted(CameraDevice camera,
                      CaptureRequest request, android.hardware.camera2.TotalCaptureResult result) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureCompleted(
                                      CameraCaptureSessionImpl.this, request, result));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
  
              @Override
              public void onCaptureFailed(CameraDevice camera,
                      CaptureRequest request, android.hardware.camera2.CaptureFailure failure) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureFailed(
                                      CameraCaptureSessionImpl.this, request, failure));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
  
              @Override
              public void onCaptureSequenceCompleted(CameraDevice camera,
                      int sequenceId, long frameNumber) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureSequenceCompleted(
                                      CameraCaptureSessionImpl.this, sequenceId, frameNumber));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
                  finishPendingSequence(sequenceId);
              }
  
              @Override
              public void onCaptureSequenceAborted(CameraDevice camera,
                      int sequenceId) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureSequenceAborted(
                                      CameraCaptureSessionImpl.this, sequenceId));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
                  finishPendingSequence(sequenceId);
              }
  
              @Override
              public void onCaptureBufferLost(CameraDevice camera,
                      CaptureRequest request, Surface target, long frameNumber) {
                  if ((callback != null) && (executor != null)) {
                      final long ident = Binder.clearCallingIdentity();
                      try {
                          executor.execute(() -> callback.onCaptureBufferLost(
                                      CameraCaptureSessionImpl.this, request, target, frameNumber));
                      } finally {
                          Binder.restoreCallingIdentity(ident);
                      }
                  }
              }
          };
      }
  
      /**
       *
       * Create an internal state callback, to be invoked on the mDeviceExecutor
       *
       * <p>It has a few behaviors:
       * <ul>
       * <li>Convert device state changes into session state changes.
       * <li>Keep track of async tasks that the session began (idle, abort).
       * </ul>
       * </p>
       * */
      @Override
      public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() {
          final CameraCaptureSession session = this;
          final Object interfaceLock = mDeviceImpl.mInterfaceLock;
  
  
          return new CameraDeviceImpl.StateCallbackKK() {
              private boolean mBusy = false;
              private boolean mActive = false;
  
              @Override
              public void onOpened(CameraDevice camera) {
                  throw new AssertionError("Camera must already be open before creating a session");
              }
  
              @Override
              public void onDisconnected(CameraDevice camera) {
                  if (DEBUG) Log.v(TAG, mIdString + "onDisconnected");
                  close();
              }
  
              @Override
              public void onError(CameraDevice camera, int error) {
                  // Should not be reached, handled by device code
                  Log.wtf(TAG, mIdString + "Got device error " + error);
              }
  
              @Override
              public void onActive(CameraDevice camera) {
                  mIdleDrainer.taskStarted();
                  mActive = true;
  
                  if (DEBUG) Log.v(TAG, mIdString + "onActive");
                  mStateCallback.onActive(session);
              }
  
              @Override
              public void onIdle(CameraDevice camera) {
                  boolean isAborting;
                  if (DEBUG) Log.v(TAG, mIdString + "onIdle");
  
                  synchronized (interfaceLock) {
                      isAborting = mAborting;
                  }
  
                  /*
                   * Check which states we transitioned through:
                   *
                   * (ACTIVE -> IDLE)
                   * (BUSY -> IDLE)
                   *
                   * Note that this is also legal:
                   * (ACTIVE -> BUSY -> IDLE)
                   *
                   * and mark those tasks as finished
                   */
                  if (mBusy && isAborting) {
                      mAbortDrainer.taskFinished();
  
                      synchronized (interfaceLock) {
                          mAborting = false;
                      }
                  }
  
                  if (mActive) {
                      mIdleDrainer.taskFinished();
                  }
  
                  mBusy = false;
                  mActive = false;
  
                  mStateCallback.onReady(session);
              }
  
              @Override
              public void onBusy(CameraDevice camera) {
                  mBusy = true;
  
                  // TODO: Queue captures during abort instead of failing them
                  // since the app won't be able to distinguish the two actives
                  // Don't signal the application since there's no clean mapping here
                  if (DEBUG) Log.v(TAG, mIdString + "onBusy");
              }
  
              @Override
              public void onUnconfigured(CameraDevice camera) {
                  if (DEBUG) Log.v(TAG, mIdString + "onUnconfigured");
              }
  
              @Override
              public void onRequestQueueEmpty() {
                  if (DEBUG) Log.v(TAG, mIdString + "onRequestQueueEmpty");
                  mStateCallback.onCaptureQueueEmpty(session);
              }
  
              @Override
              public void onSurfacePrepared(Surface surface) {
                  if (DEBUG) Log.v(TAG, mIdString + "onSurfacePrepared");
                  mStateCallback.onSurfacePrepared(session, surface);
              }
          };
  
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              close();
          } finally {
              super.finalize();
          }
      }
  
      private void checkNotClosed() {
          if (mClosed) {
              throw new IllegalStateException(
                      "Session has been closed; further changes are illegal.");
          }
      }
  
      /**
       * Notify the session that a pending capture sequence has just been queued.
       *
       * <p>During a shutdown/close, the session waits until all pending sessions are finished
       * before taking any further steps to shut down itself.</p>
       *
       * @see #finishPendingSequence
       */
      private int addPendingSequence(int sequenceId) {
          mSequenceDrainer.taskStarted(sequenceId);
          return sequenceId;
      }
  
      /**
       * Notify the session that a pending capture sequence is now finished.
       *
       * <p>During a shutdown/close, once all pending sequences finish, it is safe to
       * close the camera further by unconfiguring and then firing {@code onClosed}.</p>
       */
      private void finishPendingSequence(int sequenceId) {
          try {
              mSequenceDrainer.taskFinished(sequenceId);
          } catch (IllegalStateException e) {
              // Workaround for b/27870771
              Log.w(TAG, e.getMessage());
          }
      }
  
      private class SequenceDrainListener implements TaskDrainer.DrainListener {
          @Override
          public void onDrained() {
              /*
               * No repeating request is set; and the capture queue has fully drained.
               *
               * If no captures were queued to begin with, and an abort was queued,
               * it's still possible to get another BUSY before the last IDLE.
               *
               * If the camera is already "IDLE" and no aborts are pending,
               * then the drain immediately finishes.
               */
              if (DEBUG) Log.v(TAG, mIdString + "onSequenceDrained");
  
  
              // Fire session close as soon as all sequences are complete.
              // We may still need to unconfigure the device, but a new session might be created
              // past this point, and notifications would then stop to this instance.
              mStateCallback.onClosed(CameraCaptureSessionImpl.this);
  
              // Fast path: A new capture session has replaced this one; don't wait for abort/idle
              // as we won't get state updates any more anyway.
              if (mSkipUnconfigure) {
                  return;
              }
  
              mAbortDrainer.beginDrain();
          }
      }
  
      private class AbortDrainListener implements TaskDrainer.DrainListener {
          @Override
          public void onDrained() {
              if (DEBUG) Log.v(TAG, mIdString + "onAbortDrained");
              synchronized (mDeviceImpl.mInterfaceLock) {
                  /*
                   * Any queued aborts have now completed.
                   *
                   * It's now safe to wait to receive the final "IDLE" event, as the camera device
                   * will no longer again transition to "ACTIVE" by itself.
                   *
                   * If the camera is already "IDLE", then the drain immediately finishes.
                   */
  
                  // Fast path: A new capture session has replaced this one; don't wait for idle
                  // as we won't get state updates any more anyway.
                  if (mSkipUnconfigure) {
                      return;
                  }
                  mIdleDrainer.beginDrain();
              }
          }
      }
  
      private class IdleDrainListener implements TaskDrainer.DrainListener {
          @Override
          public void onDrained() {
              if (DEBUG) Log.v(TAG, mIdString + "onIdleDrained");
  
              // Take device lock before session lock so that we can call back into device
              // without causing a deadlock
              synchronized (mDeviceImpl.mInterfaceLock) {
                  /*
                   * The device is now IDLE, and has settled. It will not transition to
                   * ACTIVE or BUSY again by itself.
                   *
                   * It's now safe to unconfigure the outputs.
                   *
                   * This operation is idempotent; a session will not be closed twice.
                   */
                  if (DEBUG)
                      Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
                              mSkipUnconfigure);
  
                  // Fast path: A new capture session has replaced this one; don't wait for idle
                  // as we won't get state updates any more anyway.
                  if (mSkipUnconfigure) {
                      return;
                  }
  
                  // Final slow path: unconfigure the camera, no session has replaced us and
                  // everything is idle.
                  try {
                      // begin transition to unconfigured
                      mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
                              /*operatingMode*/ ICameraDeviceUser.NORMAL_MODE,
                              /*sessionParams*/ null);
                  } catch (CameraAccessException e) {
                      // OK: do not throw checked exceptions.
                      Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
  
                      // TODO: call onError instead of onClosed if this happens
                  } catch (IllegalStateException e) {
                      // Camera is already closed, so nothing left to do
                      if (DEBUG) Log.v(TAG, mIdString +
                              "Camera was already closed or busy, skipping unconfigure");
                  }
              }
          }
      }
  
  }
  