content: /*
   * Copyright (C) 2015 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.params;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SystemApi;
  import android.graphics.ImageFormat;
  import android.hardware.camera2.CameraCaptureSession;
  import android.hardware.camera2.CameraDevice;
  import android.hardware.camera2.utils.HashCodeHelpers;
  import android.hardware.camera2.utils.SurfaceUtils;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.util.Log;
  import android.util.Size;
  import android.view.Surface;
  
  import static com.android.internal.util.Preconditions.*;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Objects;
  
  /**
   * A class for describing camera output, which contains a {@link Surface} and its specific
   * configuration for creating capture session.
   *
   * <p>There are several ways to instantiate, modify and use OutputConfigurations. The most common
   * and recommended usage patterns are summarized in the following list:</p>
   *<ul>
   * <li>Passing a {@link Surface} to the constructor and using the OutputConfiguration instance as
   * argument to {@link CameraDevice#createCaptureSessionByOutputConfigurations}. This is the most
   * frequent usage and clients should consider it first before other more complicated alternatives.
   * </li>
   *
   * <li>Passing only a surface source class as an argument to the constructor. This is usually
   * followed by a call to create a capture session
   * (see {@link CameraDevice#createCaptureSessionByOutputConfigurations} and a {@link Surface} add
   * call {@link #addSurface} with a valid {@link Surface}. The sequence completes with
   * {@link CameraCaptureSession#finalizeOutputConfigurations}. This is the deferred usage case which
   * aims to enhance performance by allowing the resource-intensive capture session create call to
   * execute in parallel with any {@link Surface} initialization, such as waiting for a
   * {@link android.view.SurfaceView} to be ready as part of the UI initialization.</li>
   *
   * <li>The third and most complex usage pattern involves surface sharing. Once instantiated an
   * OutputConfiguration can be enabled for surface sharing via {@link #enableSurfaceSharing}. This
   * must be done before creating a new capture session and enables calls to
   * {@link CameraCaptureSession#updateOutputConfiguration}. An OutputConfiguration with enabled
   * surface sharing can be modified via {@link #addSurface} or {@link #removeSurface}. The updates
   * to this OutputConfiguration will only come into effect after
   * {@link CameraCaptureSession#updateOutputConfiguration} returns without throwing exceptions.
   * Such updates can be done as long as the session is active. Clients should always consider the
   * additional requirements and limitations placed on the output surfaces (for more details see
   * {@link #enableSurfaceSharing}, {@link #addSurface}, {@link #removeSurface},
   * {@link CameraCaptureSession#updateOutputConfiguration}). A trade-off exists between additional
   * complexity and flexibility. If exercised correctly surface sharing can switch between different
   * output surfaces without interrupting any ongoing repeating capture requests. This saves time and
   * can significantly improve the user experience.</li>
   *
   * <li>Surface sharing can be used in combination with deferred surfaces. The rules from both cases
   * are combined and clients must call {@link #enableSurfaceSharing} before creating a capture
   * session. Attach and/or remove output surfaces via  {@link #addSurface}/{@link #removeSurface} and
   * finalize the configuration using {@link CameraCaptureSession#finalizeOutputConfigurations}.
   * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration
   * finalize method returns without exceptions.</li>
   *
   * </ul>
   *
   * <p> As of {@link android.os.Build.VERSION_CODES#P Android P}, all formats except
   * {@link ImageFormat#JPEG} and {@link ImageFormat#RAW_PRIVATE} can be used for sharing, subject to
   * device support. On prior API levels, only {@link ImageFormat#PRIVATE} format may be used.</p>
   *
   * @see CameraDevice#createCaptureSessionByOutputConfigurations
   *
   */
  public final class OutputConfiguration implements Parcelable {
  
      /**
       * Rotation constant: 0 degree rotation (no rotation)
       *
       * @hide
       */
      @SystemApi
      public static final int ROTATION_0 = 0;
  
      /**
       * Rotation constant: 90 degree counterclockwise rotation.
       *
       * @hide
       */
      @SystemApi
      public static final int ROTATION_90 = 1;
  
      /**
       * Rotation constant: 180 degree counterclockwise rotation.
       *
       * @hide
       */
      @SystemApi
      public static final int ROTATION_180 = 2;
  
      /**
       * Rotation constant: 270 degree counterclockwise rotation.
       *
       * @hide
       */
      @SystemApi
      public static final int ROTATION_270 = 3;
  
      /**
       * Invalid surface group ID.
       *
       *<p>An {@link OutputConfiguration} with this value indicates that the included surface
       *doesn't belong to any surface group.</p>
       */
      public static final int SURFACE_GROUP_ID_NONE = -1;
  
      /**
       * Create a new {@link OutputConfiguration} instance with a {@link Surface}.
       *
       * @param surface
       *          A Surface for camera to output to.
       *
       * <p>This constructor creates a default configuration, with a surface group ID of
       * {@value #SURFACE_GROUP_ID_NONE}.</p>
       *
       */
      public OutputConfiguration(@NonNull Surface surface) {
          this(SURFACE_GROUP_ID_NONE, surface, ROTATION_0);
      }
  
      /**
       * Unknown surface source type.
       */
      private final int SURFACE_TYPE_UNKNOWN = -1;
  
      /**
       * The surface is obtained from {@link android.view.SurfaceView}.
       */
      private final int SURFACE_TYPE_SURFACE_VIEW = 0;
  
      /**
       * The surface is obtained from {@link android.graphics.SurfaceTexture}.
       */
      private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
  
      /**
       * Maximum number of surfaces supported by one {@link OutputConfiguration}.
       *
       * <p>The combined number of surfaces added by the constructor and
       * {@link OutputConfiguration#addSurface} should not exceed this value.</p>
       *
       */
      private static final int MAX_SURFACES_COUNT = 4;
  
      /**
       * Create a new {@link OutputConfiguration} instance with a {@link Surface},
       * with a surface group ID.
       *
       * <p>
       * A surface group ID is used to identify which surface group this output surface belongs to. A
       * surface group is a group of output surfaces that are not intended to receive camera output
       * buffer streams simultaneously. The {@link CameraDevice} may be able to share the buffers used
       * by all the surfaces from the same surface group, therefore may reduce the overall memory
       * footprint. The application should only set the same set ID for the streams that are not
       * simultaneously streaming. A negative ID indicates that this surface doesn't belong to any
       * surface group. The default value is {@value #SURFACE_GROUP_ID_NONE}.</p>
       *
       * <p>For example, a video chat application that has an adaptive output resolution feature would
       * need two (or more) output resolutions, to switch resolutions without any output glitches.
       * However, at any given time, only one output is active to minimize outgoing network bandwidth
       * and encoding overhead.  To save memory, the application should set the video outputs to have
       * the same non-negative group ID, so that the camera device can share the same memory region
       * for the alternating outputs.</p>
       *
       * <p>It is not an error to include output streams with the same group ID in the same capture
       * request, but the resulting memory consumption may be higher than if the two streams were
       * not in the same surface group to begin with, especially if the outputs have substantially
       * different dimensions.</p>
       *
       * @param surfaceGroupId
       *          A group ID for this output, used for sharing memory between multiple outputs.
       * @param surface
       *          A Surface for camera to output to.
       *
       */
      public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface) {
          this(surfaceGroupId, surface, ROTATION_0);
      }
  
      /**
       * Create a new {@link OutputConfiguration} instance.
       *
       * <p>This constructor takes an argument for desired camera rotation</p>
       *
       * @param surface
       *          A Surface for camera to output to.
       * @param rotation
       *          The desired rotation to be applied on camera output. Value must be one of
       *          ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees,
       *          application should make sure corresponding surface size has width and height
       *          transposed relative to the width and height without rotation. For example,
       *          if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
       *          application should set rotation to {@code ROTATION_90} and make sure the
       *          corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
       *          throw {@code IllegalArgumentException} if device cannot perform such rotation.
       * @hide
       */
      @SystemApi
      public OutputConfiguration(@NonNull Surface surface, int rotation) {
          this(SURFACE_GROUP_ID_NONE, surface, rotation);
      }
  
      /**
       * Create a new {@link OutputConfiguration} instance, with rotation and a group ID.
       *
       * <p>This constructor takes an argument for desired camera rotation and for the surface group
       * ID.  See {@link #OutputConfiguration(int, Surface)} for details of the group ID.</p>
       *
       * @param surfaceGroupId
       *          A group ID for this output, used for sharing memory between multiple outputs.
       * @param surface
       *          A Surface for camera to output to.
       * @param rotation
       *          The desired rotation to be applied on camera output. Value must be one of
       *          ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees,
       *          application should make sure corresponding surface size has width and height
       *          transposed relative to the width and height without rotation. For example,
       *          if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
       *          application should set rotation to {@code ROTATION_90} and make sure the
       *          corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
       *          throw {@code IllegalArgumentException} if device cannot perform such rotation.
       * @hide
       */
      @SystemApi
      public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) {
          checkNotNull(surface, "Surface must not be null");
          checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
          mSurfaceGroupId = surfaceGroupId;
          mSurfaceType = SURFACE_TYPE_UNKNOWN;
          mSurfaces = new ArrayList<Surface>();
          mSurfaces.add(surface);
          mRotation = rotation;
          mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
          mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
          mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
          mConfiguredGenerationId = surface.getGenerationId();
          mIsDeferredConfig = false;
          mIsShared = false;
          mPhysicalCameraId = null;
      }
  
      /**
       * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
       * source class.
       * <p>
       * This constructor takes an argument for desired Surface size and the Surface source class
       * without providing the actual output Surface. This is used to setup an output configuration
       * with a deferred Surface. The application can use this output configuration to create a
       * session.
       * </p>
       * <p>
       * However, the actual output Surface must be set via {@link #addSurface} and the deferred
       * Surface configuration must be finalized via {@link
       * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
       * Surface target. The deferred Surface can only be obtained either from {@link
       * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
       * {@link android.graphics.SurfaceTexture} via
       * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
       * </p>
       *
       * @param surfaceSize Size for the deferred surface.
       * @param klass a non-{@code null} {@link Class} object reference that indicates the source of
       *            this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and
       *            {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
       * @throws IllegalArgumentException if the Surface source class is not supported, or Surface
       *         size is zero.
       */
      public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
          checkNotNull(klass, "surfaceSize must not be null");
          checkNotNull(klass, "klass must not be null");
          if (klass == android.view.SurfaceHolder.class) {
              mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
          } else if (klass == android.graphics.SurfaceTexture.class) {
              mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
          } else {
              mSurfaceType = SURFACE_TYPE_UNKNOWN;
              throw new IllegalArgumentException("Unknow surface source class type");
          }
  
          if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
              throw new IllegalArgumentException("Surface size needs to be non-zero");
          }
  
          mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
          mSurfaces = new ArrayList<Surface>();
          mRotation = ROTATION_0;
          mConfiguredSize = surfaceSize;
          mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
          mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
          mConfiguredGenerationId = 0;
          mIsDeferredConfig = true;
          mIsShared = false;
          mPhysicalCameraId = null;
      }
  
      /**
       * Enable multiple surfaces sharing the same OutputConfiguration
       *
       * <p>For advanced use cases, a camera application may require more streams than the combination
       * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
       * compatible surface can be attached to an OutputConfiguration so that they map to one
       * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing
       * clients should be careful when adding surface outputs that modify their input data. If such
       * case exists, camera clients should have an additional mechanism to synchronize read and write
       * access between individual consumers.</p>
       *
       * <p>Two surfaces are compatible in the below cases:</p>
       *
       * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
       * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
       *
       * <li> Surfaces with the same size, format, and dataSpace, but different Surface source classes
       * that are generally not compatible. However, on some devices, the underlying camera device is
       * able to use the same buffer layout for both surfaces. The only way to discover if this is the
       * case is to create a capture session with that output configuration. For example, if the
       * camera device uses the same private buffer format between a SurfaceView/SurfaceTexture and a
       * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
       * will succeed. Otherwise, it fails with {@link
       * CameraCaptureSession.StateCallback#onConfigureFailed}.
       * </ol>
       *
       * <p>To enable surface sharing, this function must be called before {@link
       * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link
       * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function after
       * {@link CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
       *
       * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration.
       * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
       * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
       */
      public void enableSurfaceSharing() {
          mIsShared = true;
      }
  
      /**
       * Set the id of the physical camera for this OutputConfiguration
       *
       * <p>In the case one logical camera is made up of multiple physical cameras, it could be
       * desirable for the camera application to request streams from individual physical cameras.
       * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p>
       *
       * <p>The valid physical camera ids can be queried by {@link
       * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}.
       * </p>
       *
       * <p>Passing in a null physicalCameraId means that the OutputConfiguration is for a logical
       * stream.</p>
       *
       * <p>This function must be called before {@link
       * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link
       * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function
       * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link
       * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.</p>
       *
       * <p>The surface belonging to a physical camera OutputConfiguration must not be used as input
       * or output of a reprocessing request. </p>
       */
      public void setPhysicalCameraId(@Nullable String physicalCameraId) {
          mPhysicalCameraId = physicalCameraId;
      }
  
      /**
       * Check if this configuration is for a physical camera.
       *
       * <p>This returns true if the output configuration was for a physical camera making up a
       * logical multi camera via {@link OutputConfiguration#setPhysicalCameraId}.</p>
       * @hide
       */
      public boolean isForPhysicalCamera() {
          return (mPhysicalCameraId != null);
      }
  
      /**
       * Check if this configuration has deferred configuration.
       *
       * <p>This will return true if the output configuration was constructed with surface deferred by
       * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after
       * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p>
       *
       * @return true if this configuration has deferred surface.
       * @hide
       */
      public boolean isDeferredConfiguration() {
          return mIsDeferredConfig;
      }
  
      /**
       * Add a surface to this OutputConfiguration.
       *
       * <p> This function can be called before or after {@link
       * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
       * the application must finalize the capture session with
       * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
       * after the output configurations have been finalized only in cases of enabled surface sharing
       * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
       * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
       *
       * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
       * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
       * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface},
       * or from {@link android.graphics.SurfaceTexture} via
       * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
       *
       * <p> If the OutputConfiguration was constructed by other constructors, the added
       * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
       * details of compatible surfaces.</p>
       *
       * <p> If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must
       * be called before calling this function to add a new Surface.</p>
       *
       * @param surface The surface to be added.
       * @throws IllegalArgumentException if the Surface is invalid, the Surface's
       *         dataspace/format doesn't match, or adding the Surface would exceed number of
       *         shared surfaces supported.
       * @throws IllegalStateException if the Surface was already added to this OutputConfiguration,
       *         or if the OutputConfiguration is not shared and it already has a surface associated
       *         with it.
       */
      public void addSurface(@NonNull Surface surface) {
          checkNotNull(surface, "Surface must not be null");
          if (mSurfaces.contains(surface)) {
              throw new IllegalStateException("Surface is already added!");
          }
          if (mSurfaces.size() == 1 && !mIsShared) {
              throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration");
          }
          if (mSurfaces.size() + 1 > MAX_SURFACES_COUNT) {
              throw new IllegalArgumentException("Exceeds maximum number of surfaces");
          }
  
          // This will throw IAE is the surface was abandoned.
          Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
          if (!surfaceSize.equals(mConfiguredSize)) {
              Log.w(TAG, "Added surface size " + surfaceSize +
                      " is different than pre-configured size " + mConfiguredSize +
                      ", the pre-configured size will be used.");
          }
  
          if (mConfiguredFormat != SurfaceUtils.getSurfaceFormat(surface)) {
              throw new IllegalArgumentException("The format of added surface format doesn't match");
          }
  
          // If the surface format is PRIVATE, do not enforce dataSpace because camera device may
          // override it.
          if (mConfiguredFormat != ImageFormat.PRIVATE &&
                  mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) {
              throw new IllegalArgumentException("The dataspace of added surface doesn't match");
          }
  
          mSurfaces.add(surface);
      }
  
      /**
       * Remove a surface from this OutputConfiguration.
       *
       * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
       *  OutputConfiguration. The only notable exception is the surface associated with
       *  the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
       *  or was added first in the deferred case
       *  {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
       *
       * @param surface The surface to be removed.
       *
       * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration
       *                                  (see {@link #getSurface}) or the surface didn't get added
       *                                  with {@link #addSurface}.
       */
      public void removeSurface(@NonNull Surface surface) {
          if (getSurface() == surface) {
              throw new IllegalArgumentException(
                      "Cannot remove surface associated with this output configuration");
          }
          if (!mSurfaces.remove(surface)) {
              throw new IllegalArgumentException("Surface is not part of this output configuration");
          }
      }
  
      /**
       * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
       * instance.
       *
       * @param other Another {@link OutputConfiguration} instance to be copied.
       *
       * @hide
       */
      public OutputConfiguration(@NonNull OutputConfiguration other) {
          if (other == null) {
              throw new IllegalArgumentException("OutputConfiguration shouldn't be null");
          }
  
          this.mSurfaces = other.mSurfaces;
          this.mRotation = other.mRotation;
          this.mSurfaceGroupId = other.mSurfaceGroupId;
          this.mSurfaceType = other.mSurfaceType;
          this.mConfiguredDataspace = other.mConfiguredDataspace;
          this.mConfiguredFormat = other.mConfiguredFormat;
          this.mConfiguredSize = other.mConfiguredSize;
          this.mConfiguredGenerationId = other.mConfiguredGenerationId;
          this.mIsDeferredConfig = other.mIsDeferredConfig;
          this.mIsShared = other.mIsShared;
          this.mPhysicalCameraId = other.mPhysicalCameraId;
      }
  
      /**
       * Create an OutputConfiguration from Parcel.
       */
      private OutputConfiguration(@NonNull Parcel source) {
          int rotation = source.readInt();
          int surfaceSetId = source.readInt();
          int surfaceType = source.readInt();
          int width = source.readInt();
          int height = source.readInt();
          boolean isDeferred = source.readInt() == 1;
          boolean isShared = source.readInt() == 1;
          ArrayList<Surface> surfaces = new ArrayList<Surface>();
          source.readTypedList(surfaces, Surface.CREATOR);
          String physicalCameraId = source.readString();
  
          checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
  
          mSurfaceGroupId = surfaceSetId;
          mRotation = rotation;
          mSurfaces = surfaces;
          mConfiguredSize = new Size(width, height);
          mIsDeferredConfig = isDeferred;
          mIsShared = isShared;
          mSurfaces = surfaces;
          if (mSurfaces.size() > 0) {
              mSurfaceType = SURFACE_TYPE_UNKNOWN;
              mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0));
              mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces.get(0));
              mConfiguredGenerationId = mSurfaces.get(0).getGenerationId();
          } else {
              mSurfaceType = surfaceType;
              mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
              mConfiguredDataspace =
                      StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
              mConfiguredGenerationId = 0;
          }
          mPhysicalCameraId = physicalCameraId;
      }
  
      /**
       * Get the maximum supported shared {@link Surface} count.
       *
       * @return the maximum number of surfaces that can be added per each OutputConfiguration.
       *
       * @see #enableSurfaceSharing
       */
      public int getMaxSharedSurfaceCount() {
          return MAX_SURFACES_COUNT;
      }
  
      /**
       * Get the {@link Surface} associated with this {@link OutputConfiguration}.
       *
       * If more than one surface is associated with this {@link OutputConfiguration}, return the
       * first one as specified in the constructor or {@link OutputConfiguration#addSurface}.
       */
      public @Nullable Surface getSurface() {
          if (mSurfaces.size() == 0) {
              return null;
          }
  
          return mSurfaces.get(0);
      }
  
      /**
       * Get the immutable list of surfaces associated with this {@link OutputConfiguration}.
       *
       * @return the list of surfaces associated with this {@link OutputConfiguration} as specified in
       * the constructor and {@link OutputConfiguration#addSurface}. The list should not be modified.
       */
      @NonNull
      public List<Surface> getSurfaces() {
          return Collections.unmodifiableList(mSurfaces);
      }
  
      /**
       * Get the rotation associated with this {@link OutputConfiguration}.
       *
       * @return the rotation associated with this {@link OutputConfiguration}.
       *         Value will be one of ROTATION_[0, 90, 180, 270]
       *
       * @hide
       */
      @SystemApi
      public int getRotation() {
          return mRotation;
      }
  
      /**
       * Get the surface group ID associated with this {@link OutputConfiguration}.
       *
       * @return the surface group ID associated with this {@link OutputConfiguration}.
       *         The default value is {@value #SURFACE_GROUP_ID_NONE}.
       */
      public int getSurfaceGroupId() {
          return mSurfaceGroupId;
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
              new Parcelable.Creator<OutputConfiguration>() {
          @Override
          public OutputConfiguration createFromParcel(Parcel source) {
              try {
                  OutputConfiguration outputConfiguration = new OutputConfiguration(source);
                  return outputConfiguration;
              } catch (Exception e) {
                  Log.e(TAG, "Exception creating OutputConfiguration from parcel", e);
                  return null;
              }
          }
  
          @Override
          public OutputConfiguration[] newArray(int size) {
              return new OutputConfiguration[size];
          }
      };
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          if (dest == null) {
              throw new IllegalArgumentException("dest must not be null");
          }
          dest.writeInt(mRotation);
          dest.writeInt(mSurfaceGroupId);
          dest.writeInt(mSurfaceType);
          dest.writeInt(mConfiguredSize.getWidth());
          dest.writeInt(mConfiguredSize.getHeight());
          dest.writeInt(mIsDeferredConfig ? 1 : 0);
          dest.writeInt(mIsShared ? 1 : 0);
          dest.writeTypedList(mSurfaces);
          dest.writeString(mPhysicalCameraId);
      }
  
      /**
       * Check if this {@link OutputConfiguration} is equal to another {@link OutputConfiguration}.
       *
       * <p>Two output configurations are only equal if and only if the underlying surfaces, surface
       * properties (width, height, format, dataspace) when the output configurations are created,
       * and all other configuration parameters are equal. </p>
       *
       * @return {@code true} if the objects were equal, {@code false} otherwise
       */
      @Override
      public boolean equals(Object obj) {
          if (obj == null) {
              return false;
          } else if (this == obj) {
              return true;
          } else if (obj instanceof OutputConfiguration) {
              final OutputConfiguration other = (OutputConfiguration) obj;
              if (mRotation != other.mRotation ||
                      !mConfiguredSize.equals(other.mConfiguredSize) ||
                      mConfiguredFormat != other.mConfiguredFormat ||
                      mSurfaceGroupId != other.mSurfaceGroupId ||
                      mSurfaceType != other.mSurfaceType ||
                      mIsDeferredConfig != other.mIsDeferredConfig ||
                      mIsShared != other.mIsShared ||
                      mConfiguredFormat != other.mConfiguredFormat ||
                      mConfiguredDataspace != other.mConfiguredDataspace ||
                      mConfiguredGenerationId != other.mConfiguredGenerationId ||
                      !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId))
                  return false;
  
              int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size());
              for (int i = 0;  i < minLen; i++) {
                  if (mSurfaces.get(i) != other.mSurfaces.get(i))
                      return false;
              }
  
              return true;
          }
          return false;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public int hashCode() {
          // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise
          // the deferred output configuration will be lost in the camera streammap after the deferred
          // surface is set.
          if (mIsDeferredConfig) {
              return HashCodeHelpers.hashCode(
                      mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
                      mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0,
                      mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode());
          }
  
          return HashCodeHelpers.hashCode(
                  mRotation, mSurfaces.hashCode(), mConfiguredGenerationId,
                  mConfiguredSize.hashCode(), mConfiguredFormat,
                  mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0,
                  mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode());
      }
  
      private static final String TAG = "OutputConfiguration";
      private ArrayList<Surface> mSurfaces;
      private final int mRotation;
      private final int mSurfaceGroupId;
      // Surface source type, this is only used by the deferred surface configuration objects.
      private final int mSurfaceType;
  
      // The size, format, and dataspace of the surface when OutputConfiguration is created.
      private final Size mConfiguredSize;
      private final int mConfiguredFormat;
      private final int mConfiguredDataspace;
      // Surface generation ID to distinguish changes to Surface native internals
      private final int mConfiguredGenerationId;
      // Flag indicating if this config has deferred surface.
      private final boolean mIsDeferredConfig;
      // Flag indicating if this config has shared surfaces
      private boolean mIsShared;
      // The physical camera id that this output configuration is for.
      private String mPhysicalCameraId;
  }
  