ImageCapture.java

/*
 * Copyright (C) 2019 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 androidx.camera.core;

import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_BUNDLE;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_CONFIG_UNPACKER;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_PROCESSOR;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_CAPTURE_CONFIG;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_SESSION_CONFIG;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_FLASH_MODE;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_FLASH_TYPE;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IMAGE_CAPTURE_MODE;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IMAGE_READER_PROXY_PROVIDER;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_IO_EXECUTOR;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_JPEG_COMPRESSION_QUALITY;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_MAX_CAPTURE_STAGES;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_MAX_RESOLUTION;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SESSION_CONFIG_UNPACKER;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SESSION_PROCESSOR_ENABLED;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SUPPORTED_RESOLUTIONS;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_SURFACE_OCCUPANCY_PRIORITY;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_TARGET_ASPECT_RATIO;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_TARGET_CLASS;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_TARGET_NAME;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_TARGET_RESOLUTION;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_TARGET_ROTATION;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_CASE_EVENT_CALLBACK;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER;
import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAMERA_SELECTOR;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.location.Location;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.util.Rational;
import android.util.Size;
import android.view.Display;
import android.view.Surface;

import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.ForwardingImageProxy.OnImageCloseListener;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CaptureBundle;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.CaptureProcessor;
import androidx.camera.core.impl.CaptureStage;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.ConfigProvider;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.ImageCaptureConfig;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
import androidx.camera.core.impl.ImageReaderProxy;
import androidx.camera.core.impl.ImmediateSurface;
import androidx.camera.core.impl.MutableConfig;
import androidx.camera.core.impl.MutableOptionsBundle;
import androidx.camera.core.impl.MutableTagBundle;
import androidx.camera.core.impl.OptionsBundle;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.UseCaseConfigFactory;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.camera.core.impl.utils.Exif;
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.core.internal.IoConfig;
import androidx.camera.core.internal.TargetConfig;
import androidx.camera.core.internal.YuvToJpegProcessor;
import androidx.camera.core.internal.compat.quirk.SoftwareJpegEncodingPreferredQuirk;
import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability;
import androidx.camera.core.internal.utils.ImageUtil;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.core.util.Preconditions;
import androidx.lifecycle.LifecycleOwner;

import com.google.common.util.concurrent.ListenableFuture;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A use case for taking a picture.
 *
 * <p>This class is designed for basic picture taking. It provides takePicture() functions to take
 * a picture to memory or save to a file, and provides image metadata.  Pictures are taken in
 * automatic mode after focus has converged. The flash mode can additionally be set by the
 * application.
 *
 * <p>TakePicture returns immediately and a listener is called to provide the results after the
 * capture completes. Multiple calls to takePicture will take pictures sequentially starting
 * after the previous picture is captured.
 *
 * <p>Note that focus and exposure metering regions can be controlled via {@link Preview}.
 *
 * <p>When capturing to memory, the captured image is made available through an {@link ImageProxy}
 * via an {@link ImageCapture.OnImageCapturedCallback}.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class ImageCapture extends UseCase {

    ////////////////////////////////////////////////////////////////////////////////////////////
    // [UseCase lifetime constant] - Stays constant for the lifetime of the UseCase. Which means
    // they could be created in the constructor.
    ////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * An unknown error occurred.
     *
     * <p>See message parameter in onError callback or log for more details.
     */
    public static final int ERROR_UNKNOWN = 0;
    /**
     * An error occurred while attempting to read or write a file, such as when saving an image
     * to a File.
     */
    public static final int ERROR_FILE_IO = 1;

    /**
     * An error reported by camera framework indicating the capture request is failed.
     */
    public static final int ERROR_CAPTURE_FAILED = 2;

    /**
     * An error indicating the request cannot be done due to camera is closed.
     */
    public static final int ERROR_CAMERA_CLOSED = 3;

    /**
     * An error indicating this ImageCapture is not bound to a valid camera.
     */
    public static final int ERROR_INVALID_CAMERA = 4;

    /**
     * Optimizes capture pipeline to prioritize image quality over latency. When the capture
     * mode is set to MAX_QUALITY, images may take longer to capture.
     */
    public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0;
    /**
     * Optimizes capture pipeline to prioritize latency over image quality. When the capture
     * mode is set to MIN_LATENCY, images may capture faster but the image quality may be
     * reduced.
     */
    public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1;
    /**
     * Optimizes capture pipeline to prioritize latency over image quality. When the capture
     * mode is set to ZERO_SHUTTER_LAG, the latency between the shutter button is clicked and the
     * picture is recorded is minimized.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2;

    /**
     * Auto flash. The flash will be used according to the camera system's determination when taking
     * a picture.
     */
    private static final int FLASH_MODE_UNKNOWN = -1;
    public static final int FLASH_MODE_AUTO = 0;
    /** Always flash. The flash will always be used when taking a picture. */
    public static final int FLASH_MODE_ON = 1;
    /** No flash. The flash will never be used when taking a picture. */
    public static final int FLASH_MODE_OFF = 2;

    /**
     * When flash is required for taking a picture, a normal one shot flash will be used.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    public static final int FLASH_TYPE_ONE_SHOT_FLASH = 0;
    /**
     * When flash is required for taking a picture, torch will be used as flash.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    public static final int FLASH_TYPE_USE_TORCH_AS_FLASH = 1;

    /**
     * Provides a static configuration with implementation-agnostic options.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    public static final Defaults DEFAULT_CONFIG = new Defaults();
    private static final String TAG = "ImageCapture";
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    private static final int MAX_IMAGES = 2;
    // TODO(b/149336664) Move the quality to a compatibility class when there is a per device case.
    private static final byte JPEG_QUALITY_MAXIMIZE_QUALITY_MODE = 100;
    private static final byte JPEG_QUALITY_MINIMIZE_LATENCY_MODE = 95;
    @CaptureMode
    private static final int DEFAULT_CAPTURE_MODE = CAPTURE_MODE_MINIMIZE_LATENCY;
    @FlashMode
    private static final int DEFAULT_FLASH_MODE = FLASH_MODE_OFF;

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    static final ExifRotationAvailability EXIF_ROTATION_AVAILABILITY =
            new ExifRotationAvailability();

    private final ImageReaderProxy.OnImageAvailableListener mClosingListener = (imageReader -> {
        try (ImageProxy image = imageReader.acquireLatestImage()) {
            Log.d(TAG, "Discarding ImageProxy which was inadvertently acquired: " + image);
        } catch (IllegalStateException e) {
            Log.e(TAG, "Failed to acquire latest image.", e);
        }
    });

    @NonNull
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    final Executor mIoExecutor;
    @CaptureMode
    private final int mCaptureMode;

    @GuardedBy("mLockedFlashMode")
    private final AtomicReference<Integer> mLockedFlashMode = new AtomicReference<>(null);

    @FlashType
    private final int mFlashType;

    ////////////////////////////////////////////////////////////////////////////////////////////
    // [UseCase lifetime dynamic] - Dynamic variables which could change during anytime during
    // the UseCase lifetime.
    ////////////////////////////////////////////////////////////////////////////////////////////

    /** Current flash mode. */
    @GuardedBy("mLockedFlashMode")
    @FlashMode
    private int mFlashMode = FLASH_MODE_UNKNOWN;
    private Rational mCropAspectRatio = null;

    ////////////////////////////////////////////////////////////////////////////////////////////
    // [UseCase attached constant] - Is only valid when the UseCase is attached to a camera.
    ////////////////////////////////////////////////////////////////////////////////////////////

    private ExecutorService mExecutor;

    private CaptureConfig mCaptureConfig;

    /** The set of requests that will be sent to the camera for the final captured image. */
    private CaptureBundle mCaptureBundle;
    private int mMaxCaptureStages;

    /**
     * Processing that gets done to the mCaptureBundle to produce the final image that is produced
     * by {@link #takePicture(Executor, OnImageCapturedCallback)}
     */
    private CaptureProcessor mCaptureProcessor;

    /**
     * Whether the software JPEG pipeline will be used.
     */
    private boolean mUseSoftwareJpeg = false;

    /**
     * Whether SessionProcessor is enabled.
     */
    private boolean mIsSessionProcessorEnabled = true;

    ////////////////////////////////////////////////////////////////////////////////////////////
    // [UseCase attached dynamic] - Can change but is only available when the UseCase is attached.
    ////////////////////////////////////////////////////////////////////////////////////////////

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
            SessionConfig.Builder mSessionConfigBuilder;

    /** synthetic accessor */
    @SuppressWarnings("WeakerAccess")
    SafeCloseImageReaderProxy mImageReader;

    @SuppressWarnings("WeakerAccess")
    ProcessingImageReader mProcessingImageReader;

    private ListenableFuture<Void> mImageReaderCloseFuture = Futures.immediateFuture(null);

    /** Callback used to match the {@link ImageProxy} with the {@link ImageInfo}. */
    private CameraCaptureCallback mMetadataMatchingCaptureCallback;

    private DeferrableSurface mDeferrableSurface;

    private ImageCaptureRequestProcessor mImageCaptureRequestProcessor;
    // Synthetic access
    @SuppressWarnings("WeakerAccess")
    final Executor mSequentialIoExecutor;

    private Matrix mSensorToBufferTransformMatrix = new Matrix();

    /**
     * Creates a new image capture use case from the given configuration.
     *
     * @param userConfig for this use case instance
     * @throws IllegalArgumentException if the configuration is invalid.
     */
    ImageCapture(@NonNull ImageCaptureConfig userConfig) {
        super(userConfig);

        ImageCaptureConfig useCaseConfig = (ImageCaptureConfig) getCurrentConfig();

        if (useCaseConfig.containsOption(OPTION_IMAGE_CAPTURE_MODE)) {
            mCaptureMode = useCaseConfig.getCaptureMode();
        } else {
            mCaptureMode = DEFAULT_CAPTURE_MODE;
        }

        mFlashType = useCaseConfig.getFlashType(FLASH_TYPE_ONE_SHOT_FLASH);

        mIoExecutor = Preconditions.checkNotNull(
                useCaseConfig.getIoExecutor(CameraXExecutors.ioExecutor()));
        mSequentialIoExecutor = CameraXExecutors.newSequentialExecutor(mIoExecutor);

    }

    @UiThread
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    SessionConfig.Builder createPipeline(@NonNull String cameraId,
            @NonNull ImageCaptureConfig config, @NonNull Size resolution) {
        Threads.checkMainThread();
        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
        YuvToJpegProcessor softwareJpegProcessor = null;

        if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
            getCameraControl().addZslConfig(resolution, sessionConfigBuilder);
        }

        // Setup the ImageReader to do processing
        if (config.getImageReaderProxyProvider() != null) {
            mImageReader =
                    new SafeCloseImageReaderProxy(
                            config.getImageReaderProxyProvider().newInstance(resolution.getWidth(),
                                    resolution.getHeight(), getImageFormat(), MAX_IMAGES, 0));
            mMetadataMatchingCaptureCallback = new CameraCaptureCallback() {
            };
        } else if (mIsSessionProcessorEnabled) {
            ImageReaderProxy imageReader;
            if (getImageFormat() == ImageFormat.JPEG) {
                imageReader =
                        new AndroidImageReaderProxy(ImageReader.newInstance(resolution.getWidth(),
                                resolution.getHeight(), getImageFormat(), MAX_IMAGES));
            } else if (getImageFormat() == ImageFormat.YUV_420_888) { // convert it into Jpeg
                if (Build.VERSION.SDK_INT >= 26) {
                    // Jpeg rotation / quality will be set to softwareJpegProcessor later in
                    // ImageCaptureRequestProcessor.
                    softwareJpegProcessor =
                            new YuvToJpegProcessor(getJpegQualityInternal(), MAX_IMAGES);

                    ModifiableImageReaderProxy inputReader =
                            new ModifiableImageReaderProxy(
                                    ImageReader.newInstance(resolution.getWidth(),
                                            resolution.getHeight(),
                                            ImageFormat.YUV_420_888,
                                            MAX_IMAGES));

                    CaptureBundle captureBundle = CaptureBundles.singleDefaultCaptureBundle();
                    ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
                            inputReader,
                            captureBundle,
                            softwareJpegProcessor
                    ).setPostProcessExecutor(mExecutor).setOutputFormat(ImageFormat.JPEG).build();

                    // Ensure the ImageProxy contains the same capture stage id expected from the
                    // ProcessingImageReader.
                    MutableTagBundle tagBundle = MutableTagBundle.create();
                    tagBundle.putTag(processingImageReader.getTagBundleKey(),
                            captureBundle.getCaptureStages().get(0).getId());
                    inputReader.setImageTagBundle(tagBundle);

                    imageReader = processingImageReader;
                } else {
                    throw new UnsupportedOperationException("Does not support API level < 26");
                }
            } else {
                throw new IllegalArgumentException("Unsupported image format:" + getImageFormat());
            }
            mMetadataMatchingCaptureCallback = new CameraCaptureCallback() {};
            mImageReader = new SafeCloseImageReaderProxy(imageReader);
        } else if (mCaptureProcessor != null || mUseSoftwareJpeg) {
            // Capture processor set from configuration takes precedence over software JPEG.
            CaptureProcessor captureProcessor = mCaptureProcessor;
            int inputFormat = getImageFormat();
            int outputFormat = getImageFormat();
            if (mUseSoftwareJpeg) {
                // API check to satisfy linter
                if (Build.VERSION.SDK_INT >= 26) {
                    Logger.i(TAG, "Using software JPEG encoder.");

                    if (mCaptureProcessor != null) {
                        softwareJpegProcessor = new YuvToJpegProcessor(getJpegQualityInternal(),
                                mMaxCaptureStages);
                        captureProcessor = new CaptureProcessorPipeline(
                                mCaptureProcessor, mMaxCaptureStages, softwareJpegProcessor,
                                mExecutor);
                    } else {
                        captureProcessor = softwareJpegProcessor =
                                new YuvToJpegProcessor(getJpegQualityInternal(), mMaxCaptureStages);
                    }

                    outputFormat = ImageFormat.JPEG;
                } else {
                    // Note: This should never be hit due to SDK_INT check before setting
                    // useSoftwareJpeg.
                    throw new IllegalStateException("Software JPEG only supported on API 26+");
                }
            }

            // TODO: To allow user to use an Executor for the image processing.
            mProcessingImageReader = new ProcessingImageReader.Builder(
                    resolution.getWidth(),
                    resolution.getHeight(),
                    inputFormat,
                    mMaxCaptureStages,
                    getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle()),
                    captureProcessor
            ).setPostProcessExecutor(mExecutor).setOutputFormat(outputFormat).build();

            mMetadataMatchingCaptureCallback = mProcessingImageReader.getCameraCaptureCallback();
            mImageReader = new SafeCloseImageReaderProxy(mProcessingImageReader);
        } else {
            MetadataImageReader metadataImageReader = new MetadataImageReader(resolution.getWidth(),
                    resolution.getHeight(), getImageFormat(), MAX_IMAGES);
            mMetadataMatchingCaptureCallback = metadataImageReader.getCameraCaptureCallback();
            mImageReader = new SafeCloseImageReaderProxy(metadataImageReader);
        }

        if (mImageCaptureRequestProcessor != null) {
            mImageCaptureRequestProcessor.cancelRequests(
                    new CancellationException("Request is canceled."));
        }

        final YuvToJpegProcessor finalSoftwareJpegProcessor = softwareJpegProcessor;

        mImageCaptureRequestProcessor = new ImageCaptureRequestProcessor(MAX_IMAGES,
                request -> takePictureInternal(request), finalSoftwareJpegProcessor == null ? null :
                new ImageCaptureRequestProcessor.RequestProcessCallback() {
                    @Override
                    public void onPreProcessRequest(
                            @NonNull ImageCaptureRequest imageCaptureRequest) {
                        if (Build.VERSION.SDK_INT >= 26) {
                            // Updates output JPEG compression quality of YuvToJpegProcessor
                            // according to current request. This was determined by whether the
                            // final output image needs to be cropped (uncompress and recompress)
                            // again when the capture request was created.
                            finalSoftwareJpegProcessor.setJpegQuality(
                                    imageCaptureRequest.mJpegQuality);

                            // Updates output rotation degrees value to the YuvToJpegProcessor so
                            // that it can write the correct value to the ExifData in the output
                            // JPEG image file.
                            finalSoftwareJpegProcessor.setRotationDegrees(
                                    imageCaptureRequest.mRotationDegrees);
                        }
                    }
                });

        // By default close images that come from the listener.
        mImageReader.setOnImageAvailableListener(mClosingListener,
                CameraXExecutors.mainThreadExecutor());

        if (mDeferrableSurface != null) {
            mDeferrableSurface.close();
        }

        mDeferrableSurface = new ImmediateSurface(
                mImageReader.getSurface(), new Size(mImageReader.getWidth(),
                mImageReader.getHeight()), mImageReader.getImageFormat());

        mImageReaderCloseFuture =
                mProcessingImageReader != null ? mProcessingImageReader.getCloseFuture()
                        : Futures.immediateFuture(null);
        mDeferrableSurface.getTerminationFuture().addListener(mImageReader::safeClose,
                CameraXExecutors.mainThreadExecutor());

        sessionConfigBuilder.addNonRepeatingSurface(mDeferrableSurface);

        sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
            clearPipeline();
            // Ensure the attached camera has not changed before resetting.
            // TODO(b/143915543): Ensure this never gets called by a camera that is not attached
            //  to this use case so we don't need to do this check.
            if (isCurrentCamera(cameraId)) {
                // Only reset the pipeline when the bound camera is the same.
                mSessionConfigBuilder = createPipeline(cameraId, config, resolution);
                updateSessionConfig(mSessionConfigBuilder.build());
                notifyReset();
            }
        });

        return sessionConfigBuilder;
    }

    /**
     * Clear the internal pipeline so that the pipeline can be set up again.
     */
    @UiThread
    @SuppressWarnings("WeakerAccess")
    void clearPipeline() {
        Threads.checkMainThread();
        if (mImageCaptureRequestProcessor != null) {
            mImageCaptureRequestProcessor.cancelRequests(
                    new CancellationException("Request is canceled."));
            mImageCaptureRequestProcessor = null;
        }
        DeferrableSurface deferrableSurface = mDeferrableSurface;
        mDeferrableSurface = null;
        mImageReader = null;
        mProcessingImageReader = null;
        mImageReaderCloseFuture = Futures.immediateFuture(null);

        if (deferrableSurface != null) {
            deferrableSurface.close();
        }
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Override
    @Nullable
    public UseCaseConfig<?> getDefaultConfig(boolean applyDefaultConfig,
            @NonNull UseCaseConfigFactory factory) {
        Config captureConfig = factory.getConfig(
                UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
                getCaptureMode());

        if (applyDefaultConfig) {
            captureConfig = Config.mergeConfigs(captureConfig, DEFAULT_CONFIG.getConfig());
        }

        return captureConfig == null ? null :
                getUseCaseConfigBuilder(captureConfig).getUseCaseConfig();
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @NonNull
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Override
    public UseCaseConfig.Builder<?, ?, ?> getUseCaseConfigBuilder(@NonNull Config config) {
        return Builder.fromConfig(config);
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @NonNull
    @Override
    protected UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo,
            @NonNull UseCaseConfig.Builder<?, ?, ?> builder) {
        if (builder.getUseCaseConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null)
                != null && Build.VERSION.SDK_INT >= 29) {
            // TODO: The API level check can be removed if the ImageWriterCompat issue on API
            //  level 28 devices (b182363220/) can be resolved.
            Logger.i(TAG, "Requesting software JPEG due to a CaptureProcessor is set.");
            builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
        } else if (cameraInfo.getCameraQuirks().contains(
                SoftwareJpegEncodingPreferredQuirk.class)) {
            // Request software JPEG encoder if quirk exists on this device and the software JPEG
            // option has not already been explicitly set.
            if (!builder.getMutableConfig().retrieveOption(OPTION_USE_SOFTWARE_JPEG_ENCODER,
                    true)) {
                Logger.w(TAG, "Device quirk suggests software JPEG encoder, but it has been "
                        + "explicitly disabled.");
            } else {
                Logger.i(TAG, "Requesting software JPEG due to device quirk.");
                builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
            }
        }

        // If software JPEG is requested, disable if it can't be supported on current API level.
        boolean useSoftwareJpeg = enforceSoftwareJpegConstraints(builder.getMutableConfig());

        // Update the input format base on the other options set (mainly whether processing
        // is done)
        Integer bufferFormat = builder.getMutableConfig().retrieveOption(OPTION_BUFFER_FORMAT,
                null);
        if (bufferFormat != null) {
            Preconditions.checkArgument(
                    builder.getMutableConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null)
                            == null,
                    "Cannot set buffer format with CaptureProcessor defined.");
            builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                    useSoftwareJpeg ? ImageFormat.YUV_420_888 : bufferFormat);
        } else {
            if (builder.getMutableConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null) != null
                    || useSoftwareJpeg) {
                builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                        ImageFormat.YUV_420_888);
            } else {
                List<Pair<Integer, Size[]>> supportedSizes =
                        builder.getMutableConfig().retrieveOption(OPTION_SUPPORTED_RESOLUTIONS,
                                null);
                if (supportedSizes == null) {
                    builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG);
                } else {
                    // Use Jpeg first if supported.
                    if (isImageFormatSupported(supportedSizes, ImageFormat.JPEG)) {
                        builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                                ImageFormat.JPEG);
                    } else if (isImageFormatSupported(supportedSizes, ImageFormat.YUV_420_888)) {
                        builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                                ImageFormat.YUV_420_888);
                    }
                }
            }
        }

        Preconditions.checkArgument(
                builder.getMutableConfig().retrieveOption(OPTION_MAX_CAPTURE_STAGES, MAX_IMAGES)
                        >= 1,
                "Maximum outstanding image count must be at least 1");
        return builder.getUseCaseConfig();
    }

    private static boolean isImageFormatSupported(List<Pair<Integer, Size[]>> supportedSizes,
            int imageFormat) {
        if (supportedSizes == null) {
            return false;
        }
        for (Pair<Integer, Size[]> supportedSize : supportedSizes) {
            if (supportedSize.first.equals(imageFormat)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Configures flash mode to CameraControlInternal once it is ready.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Override
    protected void onCameraControlReady() {
        trySetFlashModeToCameraControl();
    }

    /**
     * Get the flash mode.
     *
     * @return the flashMode. Value is {@link #FLASH_MODE_AUTO}, {@link #FLASH_MODE_ON}, or
     * {@link #FLASH_MODE_OFF}.
     */
    @FlashMode
    public int getFlashMode() {
        synchronized (mLockedFlashMode) {
            return mFlashMode != FLASH_MODE_UNKNOWN ? mFlashMode
                    : ((ImageCaptureConfig) getCurrentConfig()).getFlashMode(DEFAULT_FLASH_MODE);
        }
    }

    /**
     * Set the flash mode.
     *
     * <p>The flash control for the subsequent photo capture requests. Applications can check if
     * there is a flash unit via {@link CameraInfo#hasFlashUnit()} and update UI component if
     * necessary. If there is no flash unit, then calling this API will take no effect for the
     * subsequent photo capture requests and they will act like {@link #FLASH_MODE_OFF}.
     *
     * <p>When the torch is enabled via {@link CameraControl#enableTorch(boolean)}, the torch
     * will remain enabled during photo capture regardless of flashMode setting. When
     * the torch is disabled, flash will function as specified by {@link #setFlashMode(int)}.
     *
     * <p>On some LEGACY devices like Samsung A3, taking pictures with {@link #FLASH_MODE_AUTO}
     * mode could cause a crash. To workaround this CameraX will disable the auto flash behavior
     * internally on devices that have this issue.
     *
     * @param flashMode the flash mode. Value is {@link #FLASH_MODE_AUTO}, {@link #FLASH_MODE_ON},
     *                  or {@link #FLASH_MODE_OFF}.
     */
    public void setFlashMode(@FlashMode int flashMode) {
        if (flashMode != FLASH_MODE_AUTO && flashMode != FLASH_MODE_ON
                && flashMode != FLASH_MODE_OFF) {
            throw new IllegalArgumentException("Invalid flash mode: " + flashMode);
        }

        synchronized (mLockedFlashMode) {
            mFlashMode = flashMode;
            trySetFlashModeToCameraControl();
        }
    }

    /**
     * Sets target cropping aspect ratio for output image.
     *
     * <p>This aspect ratio is orientation-dependent. It should be expressed in the coordinate
     * frame after rotating the image by the target rotation.
     *
     * <p>This sets the cropping rectangle returned by {@link ImageProxy#getCropRect()} returned
     * from {@link ImageCapture#takePicture(Executor, OnImageCapturedCallback)}.
     *
     * <p>For example, assume the {@code aspectRatio} of 3x4. If an image has a resolution of
     * 480x640 after applying the target rotation, then the output {@link ImageProxy} of
     * {@link ImageCapture#takePicture(Executor, OnImageCapturedCallback)} would have a cropping
     * rectangle of 480x640 after applying the rotation degrees. However, if an image has a
     * resolution of 640x480 after applying the target rotation, then the cropping rectangle
     * of the output {@link ImageProxy} would be 360x480 after applying the rotation degrees.
     *
     * <p>This crops the saved image when calling
     * {@link ImageCapture#takePicture(OutputFileOptions, Executor, OnImageSavedCallback)}. Note
     * that the cropping will introduce an additional latency.
     *
     * <p>Cropping occurs around the center of the image and as though it were in the target
     * rotation. For example, assume the {@code aspectRatio} of 3x4. If an image has a resolution
     * of 480x640 after applying the target rotation, then the saved output image would be
     * 480x640 after applying the EXIF orientation value. However, if an image has a resolution
     * of 640x480 after applying the target rotation, then the saved output image would be
     * 360x480 after applying the EXIF orientation value.
     *
     * <p>This setting value will be automatically updated to match the new target rotation value
     * when {@link ImageCapture#setTargetRotation(int)} is called.
     *
     * @param aspectRatio New target aspect ratio.
     */
    public void setCropAspectRatio(@NonNull Rational aspectRatio) {
        mCropAspectRatio = aspectRatio;
    }

    /**
     * Returns the desired rotation of the output image.
     *
     * <p>The rotation can be set prior to constructing an ImageCapture using
     * {@link ImageCapture.Builder#setTargetRotation(int)} or dynamically by calling
     * {@link ImageCapture#setTargetRotation(int)}. The rotation of an image taken is determined
     * by the rotation value set at the time image capture is initiated, such as when calling
     * {@link #takePicture(Executor, OnImageCapturedCallback)}.
     *
     * <p>If no target rotation is set by the application, it is set to the value of
     * {@link Display#getRotation()} of the default display at the time the use case is
     * created. The use case is fully created once it has been attached to a camera.
     *
     * @return The rotation of the intended target.
     */
    @RotationValue
    public int getTargetRotation() {
        return getTargetRotationInternal();
    }

    /**
     * Sets the desired rotation of the output image.
     *
     * <p>This will affect the EXIF rotation metadata in images saved by takePicture calls and the
     * {@link ImageInfo#getRotationDegrees()} value of the {@link ImageProxy} returned by
     * {@link OnImageCapturedCallback}. These will be set to be the rotation, which if applied to
     * the output image data, will make the image match target rotation specified here.
     *
     * <p>While rotation can also be set via {@link Builder#setTargetRotation(int)}, using
     * {@link ImageCapture#setTargetRotation(int)} allows the target rotation to be set dynamically.
     *
     * <p>In general, it is best to use an {@link android.view.OrientationEventListener} to
     * set the target rotation.  This way, the rotation output will indicate which way is down for
     * a given image.  This is important since display orientation may be locked by device
     * default, user setting, or app configuration, and some devices may not transition to a
     * reverse-portrait display orientation. In these cases,
     * use {@link ImageCapture#setTargetRotation} to set target rotation dynamically according to
     * the {@link android.view.OrientationEventListener}, without re-creating the use case.  Note
     * the OrientationEventListener output of degrees in the range [0..359] should be converted to
     * a surface rotation. The mapping values are listed as the following.
     * <p>{@link android.view.OrientationEventListener#ORIENTATION_UNKNOWN}: orientation == -1
     * <p>{@link Surface#ROTATION_0}: orientation >= 315 || orientation < 45
     * <p>{@link Surface#ROTATION_90}: orientation >= 225 && orientation < 315
     * <p>{@link Surface#ROTATION_180}: orientation >= 135 && orientation < 225
     * <p>{@link Surface#ROTATION_270}: orientation >= 45 && orientation < 135
     *
     * <p>When this function is called, value set by
     * {@link ImageCapture.Builder#setTargetResolution(Size)} will be updated automatically to make
     * sure the suitable resolution can be selected when the use case is bound. Value set by
     * {@link ImageCapture#setCropAspectRatio(Rational)} will also be updated automatically to
     * make sure the output image is cropped into expected aspect ratio.
     *
     * <p>If no target rotation is set by the application, it is set to the value of
     * {@link Display#getRotation()} of the default display at the time the use case is created. The
     * use case is fully created once it has been attached to a camera.
     *
     * <p>takePicture uses the target rotation at the time it begins executing (which may be delayed
     * waiting on a previous takePicture call to complete).
     *
     * @param rotation Target rotation of the output image, expressed as one of
     *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
     *                 {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
     */
    public void setTargetRotation(@RotationValue int rotation) {
        int oldRotation = getTargetRotation();

        if (setTargetRotationInternal(rotation)) {
            // For the crop aspect ratio value, the numerator and denominator of original setting
            // value will be swapped then set back. It is an orientation-dependent value that will
            // be used to crop ImageCapture's output image.
            if (mCropAspectRatio != null) {
                int oldRotationDegrees = CameraOrientationUtil.surfaceRotationToDegrees(
                        oldRotation);
                int newRotationDegrees = CameraOrientationUtil.surfaceRotationToDegrees(rotation);
                mCropAspectRatio = ImageUtil.getRotatedAspectRatio(
                        Math.abs(newRotationDegrees - oldRotationDegrees), mCropAspectRatio);
            }

            // TODO(b/122846516): Update session configuration and possibly reconfigure session.
        }
    }

    /**
     * Returns the set capture mode.
     *
     * <p>This is set when constructing an ImageCapture using
     * {@link ImageCapture.Builder#setCaptureMode(int)}. This is static for an instance of
     * ImageCapture.
     */
    @CaptureMode
    public int getCaptureMode() {
        return mCaptureMode;
    }

    /**
     * Returns the JPEG quality setting.
     *
     * <p>This is set when constructing an ImageCapture using
     * {@link ImageCapture.Builder#setJpegQuality(int)}. If not set, a default value will be set
     * according to the capture mode setting. JPEG compression quality 95 is set for
     * {@link #CAPTURE_MODE_MINIMIZE_LATENCY} and 100 is set for
     * {@link #CAPTURE_MODE_MAXIMIZE_QUALITY}. This is static for an instance of ImageCapture.
     */
    @IntRange(from = 1, to = 100)
    public int getJpegQuality() {
        return getJpegQualityInternal();
    }

    /**
     * Gets selected resolution information of the {@link ImageCapture}.
     *
     * <p>The returned {@link ResolutionInfo} will be expressed in the coordinates of the camera
     * sensor. Note that the resolution might not be the same as the resolution of the received
     * image by calling {@link #takePicture} because the received image might have been rotated
     * to the upright orientation using the target rotation setting by the device.
     *
     * <p>The resolution information might change if the use case is unbound and then rebound,
     * {@link #setTargetRotation(int)} is called to change the target rotation setting, or
     * {@link #setCropAspectRatio(Rational)} is called to change the crop aspect ratio setting.
     * The application needs to call {@link #getResolutionInfo()} again to get the latest
     * {@link ResolutionInfo} for the changes.
     *
     * @return the resolution information if the use case has been bound by the
     * {@link androidx.camera.lifecycle.ProcessCameraProvider#bindToLifecycle(LifecycleOwner
     * , CameraSelector, UseCase...)} API, or null if the use case is not bound yet.
     */
    @Nullable
    @Override
    public ResolutionInfo getResolutionInfo() {
        return super.getResolutionInfo();
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Override
    public void setSensorToBufferTransformMatrix(@NonNull Matrix sensorToBufferTransformMatrix) {
        mSensorToBufferTransformMatrix = sensorToBufferTransformMatrix;
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Nullable
    @Override
    protected ResolutionInfo getResolutionInfoInternal() {
        CameraInternal camera = getCamera();
        Size resolution = getAttachedSurfaceResolution();

        if (camera == null || resolution == null) {
            return null;
        }

        Rect cropRect = getViewPortCropRect();

        Rational cropAspectRatio = mCropAspectRatio;

        if (cropRect == null) {
            if (cropAspectRatio != null) {
                cropRect = ImageUtil.computeCropRectFromAspectRatio(resolution, cropAspectRatio);
            } else {
                cropRect = new Rect(0, 0, resolution.getWidth(), resolution.getHeight());
            }
        }

        int rotationDegrees = getRelativeRotation(camera);

        return ResolutionInfo.create(resolution, cropRect, rotationDegrees);
    }

    /**
     * Captures a new still image for in memory access.
     *
     * <p>The callback will be called only once for every invocation of this method. The listener
     * is responsible for calling {@link Image#close()} on the returned image.
     *
     * @param executor The executor in which the callback methods will be run.
     * @param callback Callback to be invoked for the newly captured image
     */
    public void takePicture(@NonNull Executor executor,
            final @NonNull OnImageCapturedCallback callback) {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            CameraXExecutors.mainThreadExecutor().execute(() -> takePicture(executor, callback));
            return;
        }

        // The captured image will be directly provided to the app via the OnImageCapturedCallback
        // callback. It won't be uncompressed and compressed again after the image is captured.
        // The JPEG quality setting will be directly provided to the HAL to compress the output
        // JPEG image.
        sendImageCaptureRequest(executor, callback, getJpegQualityInternal());
    }

    /**
     * Captures a new still image and saves to a file along with application specified metadata.
     *
     * <p> The callback will be called only once for every invocation of this method.
     *
     * <p> If the {@link ImageCapture} is in a {@link UseCaseGroup} where {@link ViewPort} is
     * set, or {@link #setCropAspectRatio} is used, the image may be cropped before saving to
     * disk which causes an additional latency.
     *
     * @param outputFileOptions  Options to store the newly captured image.
     * @param executor           The executor in which the callback methods will be run.
     * @param imageSavedCallback Callback to be called for the newly captured image.
     * @see ViewPort
     */
    public void takePicture(
            final @NonNull OutputFileOptions outputFileOptions,
            final @NonNull Executor executor,
            final @NonNull OnImageSavedCallback imageSavedCallback) {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            CameraXExecutors.mainThreadExecutor().execute(
                    () -> takePicture(outputFileOptions, executor, imageSavedCallback));
            return;
        }

        /*
         * We need to chain the following callbacks to save the image to disk:
         *
         * +-----------------------+
         * |                       |
         * |ImageCapture.          |
         * |OnImageCapturedCallback|
         * |                       |
         * +-----------+-----------+
         *             |
         *             |
         * +-----------v-----------+      +----------------------+
         * |                       |      |                      |
         * | ImageSaver.           |      | ImageCapture.        |
         * | OnImageSavedCallback  +------> OnImageSavedCallback |
         * |                       |      |                      |
         * +-----------------------+      +----------------------+
         */

        // Convert the ImageSaver.OnImageSavedCallback to ImageCapture.OnImageSavedCallback
        final ImageSaver.OnImageSavedCallback imageSavedCallbackWrapper =
                new ImageSaver.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(@NonNull OutputFileResults outputFileResults) {
                        imageSavedCallback.onImageSaved(outputFileResults);
                    }

                    @Override
                    public void onError(@NonNull ImageSaver.SaveError error,
                            @NonNull String message,
                            @Nullable Throwable cause) {
                        @ImageCaptureError int imageCaptureError = ERROR_UNKNOWN;
                        switch (error) {
                            case FILE_IO_FAILED:
                                imageCaptureError = ERROR_FILE_IO;
                                break;
                            default:
                                // Keep the imageCaptureError as UNKNOWN_ERROR
                                break;
                        }

                        imageSavedCallback.onError(
                                new ImageCaptureException(imageCaptureError, message, cause));
                    }
                };

        int outputJpegQuality = getJpegQualityInternal();

        // Wrap the ImageCapture.OnImageSavedCallback with an OnImageCapturedCallback so it can
        // be put into the capture request queue
        OnImageCapturedCallback imageCaptureCallbackWrapper =
                new OnImageCapturedCallback() {
                    @Override
                    public void onCaptureSuccess(@NonNull ImageProxy image) {
                        mIoExecutor.execute(
                                new ImageSaver(
                                        image,
                                        outputFileOptions,
                                        image.getImageInfo().getRotationDegrees(),
                                        outputJpegQuality,
                                        executor,
                                        mSequentialIoExecutor,
                                        imageSavedCallbackWrapper));
                    }

                    @Override
                    public void onError(@NonNull final ImageCaptureException exception) {
                        imageSavedCallback.onError(exception);
                    }
                };

        int rotationDegrees = getRelativeRotation(getCamera());
        Size dispatchResolution = getAttachedSurfaceResolution();
        // At this point, we can't know whether HAL will rotate the captured image or not. No
        // matter HAL will rotate the image byte array or not, it won't affect whether the final
        // image needs cropping or not. Therefore, we can still use the attached surface
        // resolution and its relative rotation degrees against to the target rotation setting to
        // calculate the possible crop rectangle and then use it to determine whether the final
        // image will need cropping or not.
        Rect cropRect = computeDispatchCropRect(getViewPortCropRect(), mCropAspectRatio,
                rotationDegrees, dispatchResolution, rotationDegrees);
        boolean shouldCropImage = ImageUtil.shouldCropImage(dispatchResolution.getWidth(),
                dispatchResolution.getHeight(), cropRect.width(), cropRect.height());
        int capturingJpegQuality;
        if (shouldCropImage) {
            // When cropping is required, jpeg compression will occur twice:
            // 1. Jpeg quality set to camera HAL by camera capture request.
            // 2. Bitmap compression during cropping process in ImageSaver.
            // Here we need to define the first compression value and be careful to lose too much
            // quality due to double compression.
            // Setting 100 for the first compression can minimize quality loss, but will result
            // in poor performance during cropping than setting 95 (see b/206348741 for more
            // detail). As a trade-off, max quality mode is set to 100, and the others are set
            // to 95.
            capturingJpegQuality = mCaptureMode == CAPTURE_MODE_MAXIMIZE_QUALITY ? 100 : 95;
        } else {
            capturingJpegQuality = outputJpegQuality;
        }

        // Always use the mainThreadExecutor for the initial callback so we don't need to double
        // post to another thread
        sendImageCaptureRequest(CameraXExecutors.mainThreadExecutor(),
                imageCaptureCallbackWrapper, capturingJpegQuality);
    }

    @NonNull
    static Rect computeDispatchCropRect(@Nullable Rect viewPortCropRect,
            @Nullable Rational cropAspectRatio, int rotationDegrees,
            @NonNull Size dispatchResolution, int dispatchRotationDegrees) {
        if (viewPortCropRect != null) {
            return ImageUtil.computeCropRectFromDispatchInfo(viewPortCropRect, rotationDegrees,
                    dispatchResolution, dispatchRotationDegrees);
        } else if (cropAspectRatio != null) {
            // Fall back to crop aspect ratio if view port is not available.
            Rational aspectRatio = cropAspectRatio;
            if ((dispatchRotationDegrees % 180) != 0) {
                aspectRatio = new Rational(
                        /* invert the ratio numerator=*/ cropAspectRatio.getDenominator(),
                        /* invert the ratio denominator=*/ cropAspectRatio.getNumerator());
            }
            if (ImageUtil.isAspectRatioValid(dispatchResolution, aspectRatio)) {
                return ImageUtil.computeCropRectFromAspectRatio(dispatchResolution,
                        aspectRatio);
            }
        }

        return new Rect(0, 0, dispatchResolution.getWidth(), dispatchResolution.getHeight());
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @UiThread
    @Override
    public void onStateDetached() {
        abortImageCaptureRequests();
    }

    @UiThread
    private void abortImageCaptureRequests() {
        if (mImageCaptureRequestProcessor != null) {
            Throwable throwable = new CameraClosedException("Camera is closed.");
            mImageCaptureRequestProcessor.cancelRequests(throwable);
        }
    }

    @UiThread
    private void sendImageCaptureRequest(@NonNull Executor callbackExecutor,
            @NonNull OnImageCapturedCallback callback,
            @IntRange(from = 1, to = 100) int jpegQuality) {

        // TODO(b/143734846): From here on, the image capture request should be
        //  self-contained and use this camera for everything. Currently the pre-capture
        //  sequence does not follow this approach and could fail if this use case is unbound
        //  or reattached to a different camera in the middle of pre-capture.
        CameraInternal attachedCamera = getCamera();
        if (attachedCamera == null) {
            // Not bound. Notify callback.
            callbackExecutor.execute(
                    () -> callback.onError(new ImageCaptureException(ERROR_INVALID_CAMERA,
                            "Not bound to a valid Camera [" + ImageCapture.this + "]", null)));
            return;
        }

        if (mImageCaptureRequestProcessor == null) {
            callbackExecutor.execute(
                    () -> callback.onError(
                            new ImageCaptureException(ERROR_UNKNOWN, "Request is canceled", null)));
            return;
        }

        mImageCaptureRequestProcessor.sendRequest(new ImageCaptureRequest(
                getRelativeRotation(attachedCamera), jpegQuality, mCropAspectRatio,
                getViewPortCropRect(), mSensorToBufferTransformMatrix, callbackExecutor,
                callback));
    }

    private void lockFlashMode() {
        synchronized (mLockedFlashMode) {
            if (mLockedFlashMode.get() != null) {
                // FlashMode is locked.
                return;
            }
            mLockedFlashMode.set(getFlashMode());
        }
    }

    void unlockFlashMode() {
        synchronized (mLockedFlashMode) {
            Integer lockedFlashMode = mLockedFlashMode.getAndSet(null);
            if (lockedFlashMode == null) {
                // FlashMode is not locked yet.
                return;
            }
            if (lockedFlashMode.intValue() != getFlashMode()) {
                // Flash Mode is changed during lock session.
                trySetFlashModeToCameraControl();
            }
        }
    }

    private void trySetFlashModeToCameraControl() {
        synchronized (mLockedFlashMode) {
            if (mLockedFlashMode.get() != null) {
                // Flash Mode is locked.
                return;
            }
            getCameraControl().setFlashMode(getFlashMode());
        }
    }

    /**
     * Gets the JPEG quality based on {@link #mCaptureMode}.
     *
     * <p> Range is 1-100; larger is higher quality.
     *
     * @return Compression quality of the captured JPEG image.
     */
    @IntRange(from = 1, to = 100)
    private int getJpegQualityInternal() {
        ImageCaptureConfig imageCaptureConfig = (ImageCaptureConfig) getCurrentConfig();

        if (imageCaptureConfig.containsOption(OPTION_JPEG_COMPRESSION_QUALITY)) {
            return imageCaptureConfig.getJpegQuality();
        }

        switch (mCaptureMode) {
            case CAPTURE_MODE_MAXIMIZE_QUALITY:
                return JPEG_QUALITY_MAXIMIZE_QUALITY_MODE;
            case CAPTURE_MODE_MINIMIZE_LATENCY:
            case CAPTURE_MODE_ZERO_SHUTTER_LAG:
                return JPEG_QUALITY_MINIMIZE_LATENCY_MODE;
            default:
                throw new IllegalStateException("CaptureMode " + mCaptureMode + " is invalid");
        }
    }

    /**
     * The take picture flow.
     *
     * <p>There are three steps to take a picture.
     *
     * <p>(1) Pre-take picture, which will trigger af/ae scan or open torch if necessary. Then check
     * 3A converged if necessary.
     *
     * <p>(2) Issue take picture single request.
     *
     * <p>(3) Post-take picture, which will cancel af/ae scan or close torch if necessary.
     */
    private ListenableFuture<ImageProxy> takePictureInternal(
            @NonNull ImageCaptureRequest imageCaptureRequest) {
        return CallbackToFutureAdapter.getFuture(
                completer -> {
                    mImageReader.setOnImageAvailableListener(
                            (imageReader) -> {
                                try {
                                    ImageProxy image = imageReader.acquireLatestImage();
                                    if (image != null) {
                                        if (!completer.set(image)) {
                                            // If the future is already complete (probably be
                                            // cancelled), then close the image.
                                            image.close();
                                        }
                                    } else {
                                        completer.setException(new IllegalStateException(
                                                "Unable to acquire image"));
                                    }
                                } catch (IllegalStateException e) {
                                    completer.setException(e);
                                }
                            },
                            CameraXExecutors.mainThreadExecutor());

                    lockFlashMode();
                    ListenableFuture<Void> future = issueTakePicture(imageCaptureRequest);

                    Futures.addCallback(future,
                            new FutureCallback<Void>() {
                                @Override
                                public void onSuccess(Void result) {
                                    unlockFlashMode();
                                }

                                @Override
                                public void onFailure(Throwable throwable) {
                                    unlockFlashMode();

                                    completer.setException(throwable);
                                }
                            },
                            mExecutor);

                    completer.addCancellationListener(() -> future.cancel(true),
                            CameraXExecutors.directExecutor());

                    return "takePictureInternal";
                });
    }

    /**
     * A processor that manages and issues the pending {@link ImageCaptureRequest}s.
     *
     * <p>It ensures that only one single {@link ImageCaptureRequest} is in progress at a time
     * and is able to process next request only when there is not over the maximum number of
     * dispatched image.
     */
    @VisibleForTesting
    static class ImageCaptureRequestProcessor implements OnImageCloseListener {
        @GuardedBy("mLock")
        private final Deque<ImageCaptureRequest> mPendingRequests = new ArrayDeque<>();

        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
        @GuardedBy("mLock")
        ImageCaptureRequest mCurrentRequest = null;

        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
        @GuardedBy("mLock")
        ListenableFuture<ImageProxy> mCurrentRequestFuture = null;

        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
        @GuardedBy("mLock")
        int mOutstandingImages = 0;

        @GuardedBy("mLock")
        private final ImageCaptor mImageCaptor;

        private final int mMaxImages;

        @Nullable
        private final RequestProcessCallback mRequestProcessCallback;

        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
        final Object mLock = new Object();

        ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor) {
            this(maxImages, imageCaptor, null);
        }

        ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor,
                @Nullable RequestProcessCallback requestProcessCallback) {
            mMaxImages = maxImages;
            mImageCaptor = imageCaptor;
            mRequestProcessCallback = requestProcessCallback;
        }

        /**
         * Sends an {@link ImageCaptureRequest} to queue.
         *
         * @param imageCaptureRequest the image request
         */
        public void sendRequest(@NonNull ImageCaptureRequest imageCaptureRequest) {
            synchronized (mLock) {
                mPendingRequests.offer(imageCaptureRequest);
                Logger.d(TAG, String.format(Locale.US,
                        "Send image capture request [current, pending] = [%d, %d]",
                        mCurrentRequest != null ? 1 : 0, mPendingRequests.size()));
                processNextRequest();
            }
        }

        /** Cancels current processing and pending requests. */
        public void cancelRequests(@NonNull Throwable throwable) {
            ImageCaptureRequest currentRequest;
            ListenableFuture<ImageProxy> currentRequestFuture;
            List<ImageCaptureRequest> pendingRequests;
            synchronized (mLock) {
                currentRequest = mCurrentRequest;
                mCurrentRequest = null;
                currentRequestFuture = mCurrentRequestFuture;
                mCurrentRequestFuture = null;
                pendingRequests = new ArrayList<>(mPendingRequests);
                mPendingRequests.clear();
            }
            if (currentRequest != null && currentRequestFuture != null) {
                currentRequest.notifyCallbackError(getError(throwable), throwable.getMessage(),
                        throwable);
                currentRequestFuture.cancel(true);
            }
            for (ImageCaptureRequest request : pendingRequests) {
                request.notifyCallbackError(getError(throwable), throwable.getMessage(), throwable);
            }
        }

        @Override
        public void onImageClose(ImageProxy image) {
            synchronized (mLock) {
                mOutstandingImages--;
                processNextRequest();
            }
        }

        @SuppressWarnings("WeakerAccess") /* synthetic accessor */
        void processNextRequest() {
            synchronized (mLock) {
                // Unable to issue request if there is currently a request in flight
                if (mCurrentRequest != null) {
                    return;
                }

                // Unable to issue request if the ImageReader has no available image buffer left.
                if (mOutstandingImages >= mMaxImages) {
                    Logger.w(TAG,
                            "Too many acquire images. Close image to be able to process next.");
                    return;
                }

                ImageCaptureRequest imageCaptureRequest = mPendingRequests.poll();
                if (imageCaptureRequest == null) {
                    return;
                }

                mCurrentRequest = imageCaptureRequest;
                if (mRequestProcessCallback != null) {
                    mRequestProcessCallback.onPreProcessRequest(mCurrentRequest);
                }
                mCurrentRequestFuture = mImageCaptor.capture(imageCaptureRequest);
                Futures.addCallback(mCurrentRequestFuture, new FutureCallback<ImageProxy>() {
                    @Override
                    public void onSuccess(@Nullable ImageProxy image) {
                        synchronized (mLock) {
                            Preconditions.checkNotNull(image);
                            SingleCloseImageProxy wrappedImage = new SingleCloseImageProxy(image);
                            wrappedImage.addOnImageCloseListener(ImageCaptureRequestProcessor.this);
                            mOutstandingImages++;
                            imageCaptureRequest.dispatchImage(wrappedImage);

                            mCurrentRequest = null;
                            mCurrentRequestFuture = null;
                            processNextRequest();
                        }
                    }

                    @Override
                    public void onFailure(Throwable t) {
                        synchronized (mLock) {
                            if (t instanceof CancellationException) {
                                // Do not trigger callback which should be done in cancelRequests()
                                // with a given throwable.
                            } else {
                                imageCaptureRequest.notifyCallbackError(getError(t),
                                        t != null ? t.getMessage() : "Unknown error", t);
                            }

                            mCurrentRequest = null;
                            mCurrentRequestFuture = null;
                            processNextRequest();
                        }
                    }
                }, CameraXExecutors.directExecutor());
            }
        }

        /** An interface of an {@link ImageProxy} captor. */
        interface ImageCaptor {
            /**
             * Captures an {@link ImageProxy} by giving a {@link ImageCaptureRequest}.
             *
             * @param imageCaptureRequest an {@link ImageCaptureRequest} contains required
             *                            parameters for this capture.
             * @return a {@link ListenableFuture represents the capture result. Cancellation to
             * the future should cancel the capture task.
             */
            @NonNull
            ListenableFuture<ImageProxy> capture(@NonNull ImageCaptureRequest imageCaptureRequest);
        }

        /**
         * An interface to provide callbacks when processing each capture request.
         */
        interface RequestProcessCallback {
            /**
             * This will be called before starting to process the
             * ImageCaptureRequest.
             */
            void onPreProcessRequest(@NonNull ImageCaptureRequest imageCaptureRequest);
        }
    }

    @NonNull
    @Override
    public String toString() {
        return TAG + ":" + getName();
    }

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    @ImageCaptureError
    static int getError(Throwable throwable) {
        if (throwable instanceof CameraClosedException) {
            return ERROR_CAMERA_CLOSED;
        } else if (throwable instanceof ImageCaptureException) {
            return ((ImageCaptureException) throwable).getImageCaptureError();
        } else {
            return ERROR_UNKNOWN;
        }
    }

    /**
     * Disables software JPEG if it is requested and the provided config and/or device
     * characteristics are incompatible.
     *
     * @return {@code true} if software JPEG will be used after applying constraints.
     */
    static boolean enforceSoftwareJpegConstraints(@NonNull MutableConfig mutableConfig) {
        // Software encoder currently only supports API 26+.
        if (mutableConfig.retrieveOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, false)) {
            boolean supported = true;
            if (Build.VERSION.SDK_INT < 26) {
                Logger.w(TAG, "Software JPEG only supported on API 26+, but current API level is "
                        + Build.VERSION.SDK_INT);
                supported = false;
            }

            Integer bufferFormat = mutableConfig.retrieveOption(OPTION_BUFFER_FORMAT, null);
            if (bufferFormat != null && bufferFormat != ImageFormat.JPEG) {
                Logger.w(TAG, "Software JPEG cannot be used with non-JPEG output buffer format.");
                supported = false;
            }

            if (!supported) {
                Logger.w(TAG, "Unable to support software JPEG. Disabling.");
                mutableConfig.insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, false);
            }

            return supported;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    @Override
    public void onDetached() {
        ListenableFuture<Void> imageReaderCloseFuture = mImageReaderCloseFuture;

        abortImageCaptureRequests();
        clearPipeline();
        mUseSoftwareJpeg = false;

        // Shutdowns the executor after mImageReader is closed. This can avoid
        // RejectedExecutionException if a ProcessingImageReader is used to processing the
        // captured images.
        ExecutorService executorService = mExecutor;
        imageReaderCloseFuture.addListener(() -> executorService.shutdown(),
                CameraXExecutors.directExecutor());
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @Override
    @RestrictTo(Scope.LIBRARY_GROUP)
    public void onAttached() {
        ImageCaptureConfig useCaseConfig = (ImageCaptureConfig) getCurrentConfig();

        CaptureConfig.Builder captureBuilder = CaptureConfig.Builder.createFrom(useCaseConfig);
        mCaptureConfig = captureBuilder.build();

        // Retrieve camera specific settings.
        mCaptureProcessor = useCaseConfig.getCaptureProcessor(null);
        mMaxCaptureStages = useCaseConfig.getMaxCaptureStages(MAX_IMAGES);
        mCaptureBundle = useCaseConfig.getCaptureBundle(
                CaptureBundles.singleDefaultCaptureBundle());

        // This will only be set to true if software JPEG was requested and
        // enforceSoftwareJpegConstraints() hasn't removed the request.
        mUseSoftwareJpeg = useCaseConfig.isSoftwareJpegEncoderRequested();
        mIsSessionProcessorEnabled = useCaseConfig.isSessionProcessorEnabled();

        CameraInternal camera = getCamera();
        Preconditions.checkNotNull(camera, "Attached camera cannot be null");

        mExecutor =
                Executors.newFixedThreadPool(
                        1,
                        new ThreadFactory() {
                            private final AtomicInteger mId = new AtomicInteger(0);

                            @Override
                            public Thread newThread(@NonNull Runnable r) {
                                return new Thread(
                                        r,
                                        CameraXThreads.TAG + "image_capture_"
                                                + mId.getAndIncrement());
                            }
                        });
    }

    /**
     * {@inheritDoc}
     *
     * @hide
     */
    @NonNull
    @Override
    @RestrictTo(Scope.LIBRARY_GROUP)
    protected Size onSuggestedResolutionUpdated(@NonNull Size suggestedResolution) {
        mSessionConfigBuilder = createPipeline(getCameraId(),
                (ImageCaptureConfig) getCurrentConfig(), suggestedResolution);

        updateSessionConfig(mSessionConfigBuilder.build());

        // In order to speed up the take picture process, notifyActive at an early stage to
        // attach the session capture callback to repeating and get capture result all the time.
        notifyActive();
        return suggestedResolution;
    }

    /**
     * Initiates a set of captures that will be used to create the output of
     * {@link #takePicture(OutputFileOptions, Executor, OnImageSavedCallback)} and its variants.
     *
     * <p> This returns a {@link ListenableFuture} whose completion indicates that the
     * captures are finished. Before the future is complete, any modification to the camera state
     * such as 3A could affect the result of the captures. After the future is complete, then it
     * is safe to reset or modify the 3A state.
     */
    ListenableFuture<Void> issueTakePicture(@NonNull ImageCaptureRequest imageCaptureRequest) {
        Logger.d(TAG, "issueTakePicture");

        final List<CaptureConfig> captureConfigs = new ArrayList<>();
        String tagBundleKey = null;

        CaptureBundle captureBundle;
        if (mProcessingImageReader != null) {
            // If the Processor is provided, check if we have valid CaptureBundle and update
            // ProcessingImageReader before actually issuing a take picture request.
            captureBundle = getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle());

            if (captureBundle == null) {
                return Futures.immediateFailedFuture(new IllegalArgumentException(
                        "ImageCapture cannot set empty CaptureBundle."));
            }

            if (mCaptureProcessor == null && captureBundle.getCaptureStages().size() > 1) {
                return Futures.immediateFailedFuture(new IllegalArgumentException(
                        "No CaptureProcessor can be found to process the images captured for "
                                + "multiple CaptureStages."));
            }

            if (captureBundle.getCaptureStages().size() > mMaxCaptureStages) {
                return Futures.immediateFailedFuture(new IllegalArgumentException(
                        "ImageCapture has CaptureStages > Max CaptureStage size"));
            }

            mProcessingImageReader.setCaptureBundle(captureBundle);
            mProcessingImageReader.setOnProcessingErrorCallback(
                    CameraXExecutors.directExecutor(),
                    (message, cause) -> {
                        Logger.e(TAG, "Processing image failed! " + message);
                        imageCaptureRequest.notifyCallbackError(ERROR_CAPTURE_FAILED, message,
                                cause);
                    });
            tagBundleKey = mProcessingImageReader.getTagBundleKey();
        } else {
            captureBundle = getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle());
            if (captureBundle.getCaptureStages().size() > 1) {
                return Futures.immediateFailedFuture(new IllegalArgumentException(
                        "ImageCapture have no CaptureProcess set with CaptureBundle size > 1."));
            }
        }

        for (final CaptureStage captureStage : captureBundle.getCaptureStages()) {
            final CaptureConfig.Builder builder = new CaptureConfig.Builder();
            builder.setTemplateType(mCaptureConfig.getTemplateType());

            // Add the default implementation options of ImageCapture
            builder.addImplementationOptions(mCaptureConfig.getImplementationOptions());
            builder.addAllCameraCaptureCallbacks(
                    mSessionConfigBuilder.getSingleCameraCaptureCallbacks());

            builder.addSurface(mDeferrableSurface);

            // Only sets the JPEG rotation and quality capture request options when capturing
            // images in JPEG format. Some devices do not handle these CaptureRequest key values
            // when capturing a non-JPEG image. Setting these capture requests and checking the
            // returned capture results for specific purpose might cause problems. See b/204375890.
            if (getImageFormat() == ImageFormat.JPEG) {
                // Add the dynamic implementation options of ImageCapture
                if (EXIF_ROTATION_AVAILABILITY.isRotationOptionSupported()) {
                    builder.addImplementationOption(CaptureConfig.OPTION_ROTATION,
                            imageCaptureRequest.mRotationDegrees);
                }
                builder.addImplementationOption(CaptureConfig.OPTION_JPEG_QUALITY,
                        imageCaptureRequest.mJpegQuality);
            }

            // Add the implementation options required by the CaptureStage
            builder.addImplementationOptions(
                    captureStage.getCaptureConfig().getImplementationOptions());

            // Use CaptureBundle object as the key for TagBundle
            if (tagBundleKey != null) {
                builder.addTag(tagBundleKey, captureStage.getId());
            }
            builder.addCameraCaptureCallback(mMetadataMatchingCaptureCallback);
            captureConfigs.add(builder.build());
        }

        return Futures.transform(getCameraControl().submitStillCaptureRequests(
                captureConfigs, mCaptureMode, mFlashType),
                input -> null, CameraXExecutors.directExecutor());
    }

    private CaptureBundle getCaptureBundle(CaptureBundle defaultCaptureBundle) {
        List<CaptureStage> captureStages = mCaptureBundle.getCaptureStages();
        if (captureStages == null || captureStages.isEmpty()) {
            return defaultCaptureBundle;
        }

        return CaptureBundles.createCaptureBundle(captureStages);
    }

    /**
     * Describes the error that occurred during an image capture operation (such as {@link
     * ImageCapture#takePicture(Executor, OnImageCapturedCallback)}).
     *
     * <p>This is a parameter sent to the error callback functions set in listeners such as {@link
     * ImageCapture.OnImageSavedCallback#onError(ImageCaptureException)}.
     *
     * @hide
     */
    @IntDef({ERROR_UNKNOWN, ERROR_FILE_IO, ERROR_CAPTURE_FAILED, ERROR_CAMERA_CLOSED,
            ERROR_INVALID_CAMERA})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(Scope.LIBRARY_GROUP)
    public @interface ImageCaptureError {
    }

    /**
     * Capture mode options for ImageCapture. A picture will always be taken regardless of
     * mode, and the mode will be used on devices that support it.
     *
     * @hide
     */
    @IntDef({CAPTURE_MODE_MAXIMIZE_QUALITY, CAPTURE_MODE_MINIMIZE_LATENCY,
            CAPTURE_MODE_ZERO_SHUTTER_LAG})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(Scope.LIBRARY_GROUP)
    public @interface CaptureMode {
    }

    /**
     * The flash mode options when taking a picture using ImageCapture.
     *
     * <p>Applications can check if there is a flash unit via {@link CameraInfo#hasFlashUnit()} and
     * update UI component if necessary. If there is no flash unit, then the FlashMode set to
     * {@link #setFlashMode(int)} will take no effect for the subsequent photo capture requests
     * and they will act like {@link #FLASH_MODE_OFF}.
     *
     * <p>When the torch is enabled via {@link CameraControl#enableTorch(boolean)}, the torch
     * will remain enabled during photo capture regardless of flash mode setting. When
     * the torch is disabled, flash will function as specified by
     * {@link #setFlashMode(int)}.
     *
     * @hide
     */
    @IntDef({FLASH_MODE_UNKNOWN, FLASH_MODE_AUTO, FLASH_MODE_ON, FLASH_MODE_OFF})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @interface FlashMode {
    }

    /**
     * The flash type options when flash is required for taking a picture.
     *
     * @hide
     */
    @IntDef({FLASH_TYPE_ONE_SHOT_FLASH, FLASH_TYPE_USE_TORCH_AS_FLASH})
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @interface FlashType {
    }

    /** Listener containing callbacks for image file I/O events. */
    public interface OnImageSavedCallback {
        /** Called when an image has been successfully saved. */
        void onImageSaved(@NonNull OutputFileResults outputFileResults);

        /**
         * Called when an error occurs while attempting to save an image.
         *
         * @param exception An {@link ImageCaptureException} that contains the type of error, the
         *                  error message and the throwable that caused it.
         */
        void onError(@NonNull ImageCaptureException exception);
    }

    /**
     * Callback for when an image capture has completed.
     */
    public abstract static class OnImageCapturedCallback {
        /**
         * Callback for when the image has been captured.
         *
         * <p>The application is responsible for calling {@link ImageProxy#close()} to close the
         * image.
         *
         * <p>The image is of format {@link ImageFormat#JPEG}, queryable via
         * {@link ImageProxy#getFormat()}.
         *
         * <p>The image is provided as captured by the underlying {@link ImageReader} without
         * rotation applied. The value in {@code image.getImageInfo().getRotationDegrees()}
         * describes the magnitude of clockwise rotation, which if applied to the image will make
         * it match the currently configured target rotation.
         *
         * <p>For example, if the current target rotation is set to the display rotation,
         * rotationDegrees is the rotation to apply to the image to match the display orientation.
         * A rotation of 90 degrees would mean rotating the image 90 degrees clockwise produces an
         * image that will match the display orientation.
         *
         * <p>See also {@link Builder#setTargetRotation(int)} and
         * {@link #setTargetRotation(int)}.
         *
         * <p>Timestamps are in nanoseconds and monotonic and can be compared to timestamps from
         * images produced from UseCases bound to the same camera instance.  More detail is
         * available depending on the implementation.  For example with CameraX using a
         * {@link androidx.camera.camera2} implementation additional detail can be found in
         * {@link android.hardware.camera2.CameraDevice} documentation.
         *
         * @param image The captured image
         */
        public void onCaptureSuccess(@NonNull ImageProxy image) {
        }

        /**
         * Callback for when an error occurred during image capture.
         *
         * @param exception An {@link ImageCaptureException} that contains the type of error, the
         *                  error message and the throwable that caused it.
         */
        public void onError(@NonNull final ImageCaptureException exception) {
        }
    }

    /**
     * Provides a base static default configuration for the ImageCapture
     *
     * <p>These values may be overridden by the implementation. They only provide a minimum set of
     * defaults that are implementation independent.
     *
     * @hide
     */
    @RestrictTo(Scope.LIBRARY_GROUP)
    public static final class Defaults
            implements ConfigProvider<ImageCaptureConfig> {
        private static final int DEFAULT_SURFACE_OCCUPANCY_PRIORITY = 4;
        private static final int DEFAULT_ASPECT_RATIO = AspectRatio.RATIO_4_3;

        private static final ImageCaptureConfig DEFAULT_CONFIG;

        static {
            Builder builder = new Builder().setSurfaceOccupancyPriority(
                    DEFAULT_SURFACE_OCCUPANCY_PRIORITY).setTargetAspectRatio(DEFAULT_ASPECT_RATIO);

            DEFAULT_CONFIG = builder.getUseCaseConfig();
        }

        @NonNull
        @Override
        public ImageCaptureConfig getConfig() {
            return DEFAULT_CONFIG;
        }
    }

    /**
     * Options for saving newly captured image.
     *
     * <p> this class is used to configure save location and metadata. Save location can be
     * either a {@link File}, {@link MediaStore} or a {@link OutputStream}. The metadata will be
     * stored with the saved image. For JPEG this will be included in the EXIF.
     */
    public static final class OutputFileOptions {

        @Nullable
        private final File mFile;
        @Nullable
        private final ContentResolver mContentResolver;
        @Nullable
        private final Uri mSaveCollection;
        @Nullable
        private final ContentValues mContentValues;
        @Nullable
        private final OutputStream mOutputStream;
        @NonNull
        private final Metadata mMetadata;

        OutputFileOptions(@Nullable File file,
                @Nullable ContentResolver contentResolver,
                @Nullable Uri saveCollection,
                @Nullable ContentValues contentValues,
                @Nullable OutputStream outputStream,
                @Nullable Metadata metadata) {
            mFile = file;
            mContentResolver = contentResolver;
            mSaveCollection = saveCollection;
            mContentValues = contentValues;
            mOutputStream = outputStream;
            mMetadata = metadata == null ? new Metadata() : metadata;
        }

        @Nullable
        File getFile() {
            return mFile;
        }

        @Nullable
        ContentResolver getContentResolver() {
            return mContentResolver;
        }

        @Nullable
        Uri getSaveCollection() {
            return mSaveCollection;
        }

        @Nullable
        ContentValues getContentValues() {
            return mContentValues;
        }

        @Nullable
        OutputStream getOutputStream() {
            return mOutputStream;
        }

        /**
         * Exposed internally so that CameraController can overwrite the flip horizontal flag for
         * front camera. External core API users shouldn't need this because they are the ones who
         * created the {@link Metadata}.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Metadata getMetadata() {
            return mMetadata;
        }

        /**
         * Builder class for {@link OutputFileOptions}.
         */
        public static final class Builder {
            @Nullable
            private File mFile;
            @Nullable
            private ContentResolver mContentResolver;
            @Nullable
            private Uri mSaveCollection;
            @Nullable
            private ContentValues mContentValues;
            @Nullable
            private OutputStream mOutputStream;
            @Nullable
            private Metadata mMetadata;

            /**
             * Creates options to write captured image to a {@link File}.
             *
             * @param file save location of the image.
             */
            public Builder(@NonNull File file) {
                mFile = file;
            }

            /**
             * Creates options to write captured image to {@link MediaStore}.
             *
             * Example:
             *
             * <pre>{@code
             *
             * ContentValues contentValues = new ContentValues();
             * contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "NEW_IMAGE");
             * contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
             *
             * ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(
             *         getContentResolver(),
             *         MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
             *         contentValues).build();
             *
             * }</pre>
             *
             * @param contentResolver to access {@link MediaStore}
             * @param saveCollection  The URL of the table to insert into.
             * @param contentValues   to be included in the created image file.
             */
            public Builder(@NonNull ContentResolver contentResolver,
                    @NonNull Uri saveCollection,
                    @NonNull ContentValues contentValues) {
                mContentResolver = contentResolver;
                mSaveCollection = saveCollection;
                mContentValues = contentValues;
            }

            /**
             * Creates options that write captured image to a {@link OutputStream}.
             *
             * @param outputStream save location of the image.
             */
            public Builder(@NonNull OutputStream outputStream) {
                mOutputStream = outputStream;
            }

            /**
             * Sets the metadata to be stored with the saved image.
             *
             * <p> For JPEG this will be included in the EXIF.
             *
             * @param metadata Metadata to be stored with the saved image. For JPEG this will
             *                 be included in the EXIF.
             */
            @NonNull
            public Builder setMetadata(@NonNull Metadata metadata) {
                mMetadata = metadata;
                return this;
            }

            /**
             * Builds {@link OutputFileOptions}.
             */
            @NonNull
            public OutputFileOptions build() {
                return new OutputFileOptions(mFile, mContentResolver, mSaveCollection,
                        mContentValues, mOutputStream, mMetadata);
            }
        }
    }

    /**
     * Info about the saved image file.
     */
    public static class OutputFileResults {
        @Nullable
        private Uri mSavedUri;

        OutputFileResults(@Nullable Uri savedUri) {
            mSavedUri = savedUri;
        }

        /**
         * Returns the {@link Uri} of the saved file.
         *
         * <p> Returns null if the {@link OutputFileOptions} is constructed with
         * {@link androidx.camera.core.ImageCapture.OutputFileOptions.Builder
         * #Builder(OutputStream)}.
         */
        @Nullable
        public Uri getSavedUri() {
            return mSavedUri;
        }
    }

    /** Holder class for metadata that will be saved with captured images. */
    public static final class Metadata {
        /**
         * Indicates a left-right mirroring (reflection).
         *
         * <p>The reflection is meant to be applied to the upright image (after rotation to the
         * target orientation). When saving the image to file, it is combined with the rotation
         * degrees, to generate the corresponding EXIF orientation value.
         */
        private boolean mIsReversedHorizontal;

        /**
         * Whether the mIsReversedHorizontal has been set by the app explicitly.
         */
        private boolean mIsReversedHorizontalSet = false;

        /**
         * Indicates an upside down mirroring, equivalent to a horizontal mirroring (reflection)
         * followed by a 180 degree rotation.
         *
         * <p>The reflection is meant to be applied to the upright image (after rotation to the
         * target orientation). When saving the image to file, it is combined with the rotation
         * degrees, to generate the corresponding EXIF orientation value.
         */
        private boolean mIsReversedVertical;
        /** Data representing a geographic location. */
        @Nullable
        private Location mLocation;

        /**
         * Gets left-right mirroring of the capture.
         *
         * @return true if the capture is left-right mirrored.
         */
        public boolean isReversedHorizontal() {
            return mIsReversedHorizontal;
        }

        /**
         * Returns true if {@link #setReversedHorizontal} has been called.
         *
         * <p> CameraController's default behavior is mirroring the picture when front camera is
         * used. This method is used to check if reverseHorizontal is set explicitly by the app.
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        public boolean isReversedHorizontalSet() {
            return mIsReversedHorizontalSet;
        }

        /**
         * Sets left-right mirroring of the capture.
         *
         * @param isReversedHorizontal true if the capture is left-right mirrored.
         */
        public void setReversedHorizontal(boolean isReversedHorizontal) {
            mIsReversedHorizontal = isReversedHorizontal;
            mIsReversedHorizontalSet = true;
        }

        /**
         * Gets upside-down mirroring of the capture.
         *
         * @return true if the capture is upside-down.
         */
        public boolean isReversedVertical() {
            return mIsReversedVertical;
        }

        /**
         * Sets upside-down mirroring of the capture.
         *
         * @param isReversedVertical true if the capture is upside-down.
         */
        public void setReversedVertical(boolean isReversedVertical) {
            mIsReversedVertical = isReversedVertical;
        }

        /**
         * Gets the geographic location of the capture.
         *
         * @return the geographic location.
         */
        @Nullable
        public Location getLocation() {
            return mLocation;
        }

        /**
         * Sets the geographic location of the capture.
         *
         * @param location the geographic location.
         */
        public void setLocation(@Nullable Location location) {
            mLocation = location;
        }
    }

    @VisibleForTesting
    static class ImageCaptureRequest {
        @RotationValue
        final int mRotationDegrees;
        @IntRange(from = 1, to = 100)
        final int mJpegQuality;

        private final Rational mTargetRatio;
        @NonNull
        private final Executor mListenerExecutor;
        @NonNull
        private final OnImageCapturedCallback mCallback;

        AtomicBoolean mDispatched = new AtomicBoolean(false);

        private final Rect mViewPortCropRect;

        @NonNull
        private final Matrix mSensorToBufferTransformMatrix;

        /**
         *
         * @param rotationDegrees The degrees to rotate the image buffer from sensor
         *                        coordinates into the final output coordinate space.
         * @param jpegQuality The requested output JPEG image compression quality. The value must
         *                    be in range [1..100] which larger is higher quality.
         * @param targetRatio The aspect ratio of the image in final output coordinate space.
         *                    This must be a non-negative, non-zero value.
         * @param viewPortCropRect The cropped rect of the field of view.
         * @param sensorToBufferTransformMatrix The sensor to buffer transform matrix.
         * @param executor The {@link Executor} which will be used for the listener.
         * @param callback The {@link OnImageCapturedCallback} for the quest.
         * @throws IllegalArgumentException If targetRatio is not a valid value.
         */
        ImageCaptureRequest(
                @RotationValue int rotationDegrees,
                @IntRange(from = 1, to = 100) int jpegQuality,
                Rational targetRatio,
                @Nullable Rect viewPortCropRect,
                @NonNull Matrix sensorToBufferTransformMatrix,
                @NonNull Executor executor,
                @NonNull OnImageCapturedCallback callback) {
            mRotationDegrees = rotationDegrees;
            mJpegQuality = jpegQuality;
            if (targetRatio != null) {
                Preconditions.checkArgument(!targetRatio.isZero(), "Target ratio cannot be zero");
                Preconditions.checkArgument(targetRatio.floatValue() > 0, "Target ratio must be "
                        + "positive");
            }
            mTargetRatio = targetRatio;
            mViewPortCropRect = viewPortCropRect;
            mSensorToBufferTransformMatrix = sensorToBufferTransformMatrix;
            mListenerExecutor = executor;
            mCallback = callback;
        }

        void dispatchImage(final ImageProxy image) {
            // Check to make sure image hasn't been already dispatched or error has been notified
            if (!mDispatched.compareAndSet(false, true)) {
                image.close();
                return;
            }

            Size dispatchResolution;
            int dispatchRotationDegrees = 0;

            // Retrieve the dimension and rotation values from the embedded EXIF data in the
            // captured image only if those information is available.
            if (EXIF_ROTATION_AVAILABILITY.shouldUseExifOrientation(image)) {
                // JPEG needs to have rotation/crop based on the EXIF
                try {
                    ImageProxy.PlaneProxy[] planes = image.getPlanes();
                    ByteBuffer buffer = planes[0].getBuffer();
                    Exif exif;

                    buffer.rewind();

                    byte[] data = new byte[buffer.capacity()];
                    buffer.get(data);
                    exif = Exif.createFromInputStream(new ByteArrayInputStream(data));
                    buffer.rewind();

                    dispatchResolution = new Size(exif.getWidth(), exif.getHeight());
                    dispatchRotationDegrees = exif.getRotation();
                } catch (IOException e) {
                    notifyCallbackError(ERROR_FILE_IO, "Unable to parse JPEG exif", e);
                    image.close();
                    return;
                }
            } else {
                // All other formats take the rotation based simply on the target rotation
                dispatchResolution = new Size(image.getWidth(), image.getHeight());
                dispatchRotationDegrees = mRotationDegrees;
            }

            // Construct the ImageProxy with the updated rotation & crop for the output
            ImageInfo imageInfo = ImmutableImageInfo.create(
                    image.getImageInfo().getTagBundle(),
                    image.getImageInfo().getTimestamp(),
                    dispatchRotationDegrees,
                    mSensorToBufferTransformMatrix);

            final ImageProxy dispatchedImageProxy = new SettableImageProxy(image,
                    dispatchResolution, imageInfo);

            // Update the crop rect aspect ratio after it has been rotated into the buffer
            // orientation
            Rect cropRect = computeDispatchCropRect(mViewPortCropRect, mTargetRatio,
                    mRotationDegrees, dispatchResolution, dispatchRotationDegrees);
            dispatchedImageProxy.setCropRect(cropRect);

            try {
                mListenerExecutor.execute(() -> {
                    mCallback.onCaptureSuccess(dispatchedImageProxy);
                });
            } catch (RejectedExecutionException e) {
                Logger.e(TAG, "Unable to post to the supplied executor.");

                // Unable to execute on the supplied executor, close the image.
                image.close();
            }
        }

        void notifyCallbackError(final @ImageCaptureError int imageCaptureError,
                final String message, final Throwable cause) {
            // Check to make sure image hasn't been already dispatched or error has been notified
            if (!mDispatched.compareAndSet(false, true)) {
                return;
            }

            try {
                mListenerExecutor.execute(() -> mCallback.onError(
                        new ImageCaptureException(imageCaptureError, message, cause)));
            } catch (RejectedExecutionException e) {
                Logger.e(TAG, "Unable to post to the supplied executor.");
            }
        }
    }

    /** Builder for an {@link ImageCapture}. */
    @SuppressWarnings("ObjectToString")
    public static final class Builder implements
            UseCaseConfig.Builder<ImageCapture, ImageCaptureConfig, Builder>,
            ImageOutputConfig.Builder<Builder>,
            IoConfig.Builder<Builder> {

        private final MutableOptionsBundle mMutableConfig;

        /** Creates a new Builder object. */
        public Builder() {
            this(MutableOptionsBundle.create());
        }

        private Builder(MutableOptionsBundle mutableConfig) {
            mMutableConfig = mutableConfig;

            Class<?> oldConfigClass =
                    mutableConfig.retrieveOption(TargetConfig.OPTION_TARGET_CLASS, null);
            if (oldConfigClass != null && !oldConfigClass.equals(ImageCapture.class)) {
                throw new IllegalArgumentException(
                        "Invalid target class configuration for "
                                + Builder.this
                                + ": "
                                + oldConfigClass);
            }

            setTargetClass(ImageCapture.class);
        }

        /**
         * Generates a Builder from another Config object
         *
         * @param configuration An immutable configuration to pre-populate this builder.
         * @return The new Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public static Builder fromConfig(@NonNull Config configuration) {
            return new Builder(MutableOptionsBundle.from(configuration));
        }

        /**
         * Generates a Builder from another Config object
         *
         * @param configuration An immutable configuration to pre-populate this builder.
         * @return The new Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        static Builder fromConfig(@NonNull ImageCaptureConfig configuration) {
            return new Builder(MutableOptionsBundle.from(configuration));
        }

        /**
         * {@inheritDoc}
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public MutableConfig getMutableConfig() {
            return mMutableConfig;
        }

        /**
         * {@inheritDoc}
         *
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        @Override
        public ImageCaptureConfig getUseCaseConfig() {
            return new ImageCaptureConfig(OptionsBundle.from(mMutableConfig));
        }

        /**
         * Builds an immutable {@link ImageCapture} from the current state.
         *
         * @return A {@link ImageCapture} populated with the current state.
         * @throws IllegalArgumentException if attempting to set both target aspect ratio and
         *                                  target resolution.
         */
        @Override
        @NonNull
        public ImageCapture build() {
            // Error at runtime for using both setTargetResolution and setTargetAspectRatio on
            // the same config.
            if (getMutableConfig().retrieveOption(OPTION_TARGET_ASPECT_RATIO, null) != null
                    && getMutableConfig().retrieveOption(OPTION_TARGET_RESOLUTION, null) != null) {
                throw new IllegalArgumentException(
                        "Cannot use both setTargetResolution and setTargetAspectRatio on the same "
                                + "config.");
            }

            // Update the input format base on the other options set (mainly whether processing
            // is done)
            Integer bufferFormat = getMutableConfig().retrieveOption(OPTION_BUFFER_FORMAT, null);
            if (bufferFormat != null) {
                Preconditions.checkArgument(
                        getMutableConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null) == null,
                        "Cannot set buffer format with CaptureProcessor defined.");
                getMutableConfig().insertOption(OPTION_INPUT_FORMAT, bufferFormat);
            } else {
                if (getMutableConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null) != null) {
                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.YUV_420_888);
                } else {
                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG);
                }
            }

            ImageCapture imageCapture = new ImageCapture(getUseCaseConfig());

            // Makes the crop aspect ratio match the target resolution setting as what mentioned
            // in javadoc of setTargetResolution(). When the target resolution is set, {@link
            // ImageCapture#setCropAspectRatio(Rational)} will be automatically called to set
            // corresponding value.
            Size targetResolution = getMutableConfig().retrieveOption(OPTION_TARGET_RESOLUTION,
                    null);
            if (targetResolution != null) {
                imageCapture.setCropAspectRatio(new Rational(targetResolution.getWidth(),
                        targetResolution.getHeight()));
            }

            Preconditions.checkArgument(
                    getMutableConfig().retrieveOption(OPTION_MAX_CAPTURE_STAGES, MAX_IMAGES) >= 1,
                    "Maximum outstanding image count must be at least 1");

            Preconditions.checkNotNull(getMutableConfig().retrieveOption(OPTION_IO_EXECUTOR,
                    CameraXExecutors.ioExecutor()), "The IO executor can't be null");

            if (getMutableConfig().containsOption(OPTION_FLASH_MODE)) {
                int flashMode = getMutableConfig().retrieveOption(OPTION_FLASH_MODE);

                if (flashMode != FLASH_MODE_AUTO && flashMode != FLASH_MODE_ON
                        && flashMode != FLASH_MODE_OFF) {
                    throw new IllegalArgumentException(
                            "The flash mode is not allowed to set: " + flashMode);
                }
            }

            return imageCapture;
        }

        /**
         * Sets the image capture mode.
         *
         * <p>Valid capture modes are {@link CaptureMode#CAPTURE_MODE_MINIMIZE_LATENCY}, which
         * prioritizes
         * latency over image quality, or {@link CaptureMode#CAPTURE_MODE_MAXIMIZE_QUALITY},
         * which prioritizes
         * image quality over latency.
         *
         * <p>If not set, the capture mode will default to
         * {@link CaptureMode#CAPTURE_MODE_MINIMIZE_LATENCY}.
         *
         * @param captureMode The requested image capture mode.
         * @return The current Builder.
         */
        @NonNull
        public Builder setCaptureMode(@CaptureMode int captureMode) {
            getMutableConfig().insertOption(OPTION_IMAGE_CAPTURE_MODE, captureMode);
            return this;
        }

        /**
         * Sets the flashMode.
         *
         * <p>If not set, the flash mode will default to {@link #FLASH_MODE_OFF}.
         *
         * <p>See {@link ImageCapture#setFlashMode(int)} for more information.
         *
         * @param flashMode The requested flash mode. Value is {@link #FLASH_MODE_AUTO},
         *                  {@link #FLASH_MODE_ON}, or {@link #FLASH_MODE_OFF}.
         * @return The current Builder.
         */
        @NonNull
        public Builder setFlashMode(@FlashMode int flashMode) {
            getMutableConfig().insertOption(OPTION_FLASH_MODE, flashMode);
            return this;
        }

        /**
         * Sets the {@link CaptureBundle}.
         *
         * @param captureBundle The requested capture bundle for extension.
         * @return The current Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Builder setCaptureBundle(@NonNull CaptureBundle captureBundle) {
            getMutableConfig().insertOption(OPTION_CAPTURE_BUNDLE, captureBundle);
            return this;
        }

        /**
         * Sets the {@link CaptureProcessor}.
         *
         * @param captureProcessor The requested capture processor for extension.
         * @return The current Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Builder setCaptureProcessor(@NonNull CaptureProcessor captureProcessor) {
            getMutableConfig().insertOption(OPTION_CAPTURE_PROCESSOR, captureProcessor);
            return this;
        }

        /**
         * Sets the {@link ImageFormat} of the {@link ImageProxy} returned by the
         * {@link ImageCapture.OnImageCapturedCallback}.
         *
         * <p>Warning. This could lead to an invalid configuration as image format support is per
         * device. Also, setting the buffer format in conjuncture with image capture extensions will
         * result in an invalid configuration. In this case {@link
         * ImageCapture#ImageCapture(ImageCaptureConfig)} will throw an
         * {@link IllegalArgumentException}.
         *
         * @param bufferImageFormat The image format for captured images.
         * @return The current Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Builder setBufferFormat(int bufferImageFormat) {
            getMutableConfig().insertOption(OPTION_BUFFER_FORMAT, bufferImageFormat);
            return this;
        }

        /**
         * Set the flag to indicate whether SessionProcessor is enabled.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Builder setSessionProcessorEnabled(boolean enabled) {
            getMutableConfig().insertOption(OPTION_SESSION_PROCESSOR_ENABLED, enabled);
            return this;
        }

        /**
         * Sets the max number of {@link CaptureStage}.
         *
         * @param maxCaptureStages The max CaptureStage number.
         * @return The current Builder.
         * @hide
         */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @NonNull
        public Builder setMaxCaptureStages(int maxCaptureStages) {
            getMutableConfig().insertOption(OPTION_MAX_CAPTURE_STAGES, maxCaptureStages);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setSupportedResolutions(@NonNull List<Pair<Integer, Size[]>> resolutions) {
            getMutableConfig().insertOption(OPTION_SUPPORTED_RESOLUTIONS, resolutions);
            return this;
        }

        // Implementations of TargetConfig.Builder default methods

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setTargetClass(@NonNull Class<ImageCapture> targetClass) {
            getMutableConfig().insertOption(OPTION_TARGET_CLASS, targetClass);

            // If no name is set yet, then generate a unique name
            if (null == getMutableConfig().retrieveOption(OPTION_TARGET_NAME, null)) {
                String targetName = targetClass.getCanonicalName() + "-" + UUID.randomUUID();
                setTargetName(targetName);
            }

            return this;
        }

        /**
         * Sets the name of the target object being configured, used only for debug logging.
         *
         * <p>The name should be a value that can uniquely identify an instance of the object being
         * configured.
         *
         * <p>If not set, the target name will default to a unique name automatically generated
         * with the class canonical name and random UUID.
         *
         * @param targetName A unique string identifier for the instance of the class being
         *                   configured.
         * @return the current Builder.
         */
        @Override
        @NonNull
        public Builder setTargetName(@NonNull String targetName) {
            getMutableConfig().insertOption(OPTION_TARGET_NAME, targetName);
            return this;
        }

        // Implementations of ImageOutputConfig.Builder default methods

        /**
         * Sets the aspect ratio of the intended target for images from this configuration.
         *
         * <p>The aspect ratio is the ratio of width to height in the sensor orientation.
         *
         * <p>It is not allowed to set both target aspect ratio and target resolution on the same
         * use case. Attempting so will throw an IllegalArgumentException when building the Config.
         *
         * <p>The target aspect ratio is used as a hint when determining the resulting output aspect
         * ratio which may differ from the request, possibly due to device constraints.
         * Application code should check the resulting output's resolution and the resulting
         * aspect ratio may not be exactly as requested.
         *
         * <p>If not set, resolutions with aspect ratio 4:3 will be considered in higher
         * priority.
         *
         * @param aspectRatio The desired ImageCapture {@link AspectRatio}
         * @return The current Builder.
         */
        @NonNull
        @Override
        public Builder setTargetAspectRatio(@AspectRatio.Ratio int aspectRatio) {
            getMutableConfig().insertOption(OPTION_TARGET_ASPECT_RATIO, aspectRatio);
            return this;
        }

        /**
         * Sets the rotation of the intended target for images from this configuration.
         *
         * <p>This will affect the EXIF rotation metadata in images saved by takePicture calls and
         * the {@link ImageInfo#getRotationDegrees()} value of the {@link ImageProxy} returned by
         * {@link OnImageCapturedCallback}. These will be set to be the rotation, which if
         * applied to the output image data, will make the image match the target rotation
         * specified here.
         *
         * <p>This is one of four valid values: {@link Surface#ROTATION_0}, {@link
         * Surface#ROTATION_90}, {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
         * Rotation values are relative to the "natural" rotation, {@link Surface#ROTATION_0}.
         *
         * <p>In general, it is best to additionally set the target rotation dynamically on the use
         * case.  See {@link androidx.camera.core.ImageCapture#setTargetRotation(int)} for
         * additional documentation.
         *
         * <p>If not set, the target rotation will default to the value of
         * {@link android.view.Display#getRotation()} of the default display at the time the use
         * case is created. The use case is fully created once it has been attached to a camera.
         *
         * @param rotation The rotation of the intended target.
         * @return The current Builder.
         * @see androidx.camera.core.ImageCapture#setTargetRotation(int)
         * @see android.view.OrientationEventListener
         */
        @NonNull
        @Override
        public Builder setTargetRotation(@RotationValue int rotation) {
            getMutableConfig().insertOption(OPTION_TARGET_ROTATION, rotation);
            return this;
        }

        /**
         * Sets the intended output target resolution.
         *
         * <p>The target resolution attempts to establish a minimum bound for the image resolution.
         * The actual image resolution will be the closest available resolution in size that is not
         * smaller than the target resolution, as determined by the Camera implementation. However,
         * if no resolution exists that is equal to or larger than the target resolution, the
         * nearest available resolution smaller than the target resolution will be chosen.
         * Resolutions with the same aspect ratio of the provided {@link Size} will be considered in
         * higher priority before resolutions of different aspect ratios.
         *
         * <p>It is not allowed to set both target aspect ratio and target resolution on the same
         * use case. Attempting so will throw an IllegalArgumentException when building the Config.
         *
         * <p>The resolution {@link Size} should be expressed in the coordinate frame after
         * rotating the supported sizes by the target rotation. For example, a device with
         * portrait natural orientation in natural target rotation requesting a portrait image
         * may specify 480x640, and the same device, rotated 90 degrees and targeting landscape
         * orientation may specify 640x480.
         *
         * <p>When the target resolution is set,
         * {@link ImageCapture#setCropAspectRatio(Rational)} will be automatically called to set
         * corresponding value. Such that the output image will be cropped into the desired
         * aspect ratio.
         *
         * <p>The maximum available resolution that could be selected for an {@link ImageCapture}
         * will depend on the camera device's capability.
         *
         * <p>If not set, the largest available resolution will be selected to use. Usually,
         * users will intend to get the largest still image that the camera device can support.
         *
         * <p>When using the <code>camera-camera2</code> CameraX implementation, which resolution
         * will be finally selected will depend on the camera device's hardware level and the
         * bound use cases combination. For more details see the guaranteed supported
         * configurations tables in {@link android.hardware.camera2.CameraDevice}'s
         * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#regular-capture">Regular capture</a> section.
         *
         * @param resolution The target resolution to choose from supported output sizes list.
         * @return The current Builder.
         */
        @NonNull
        @Override
        public Builder setTargetResolution(@NonNull Size resolution) {
            getMutableConfig().insertOption(OPTION_TARGET_RESOLUTION, resolution);
            return this;
        }

        /**
         * Sets the default resolution of the intended target from this configuration.
         *
         * @param resolution The default resolution to choose from supported output sizes list.
         * @return The current Builder.
         * @hide
         */
        @NonNull
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        public Builder setDefaultResolution(@NonNull Size resolution) {
            getMutableConfig().insertOption(ImageOutputConfig.OPTION_DEFAULT_RESOLUTION,
                    resolution);
            return this;
        }

        /** @hide */
        @NonNull
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        public Builder setMaxResolution(@NonNull Size resolution) {
            getMutableConfig().insertOption(OPTION_MAX_RESOLUTION, resolution);
            return this;
        }

        /** @hide */
        @NonNull
        @RestrictTo(Scope.LIBRARY_GROUP)
        public Builder setImageReaderProxyProvider(
                @NonNull ImageReaderProxyProvider imageReaderProxyProvider) {
            getMutableConfig().insertOption(OPTION_IMAGE_READER_PROXY_PROVIDER,
                    imageReaderProxyProvider);
            return this;
        }

        /** @hide */
        @NonNull
        @RestrictTo(Scope.LIBRARY_GROUP)
        public Builder setSoftwareJpegEncoderRequested(boolean requestSoftwareJpeg) {
            getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER,
                    requestSoftwareJpeg);
            return this;
        }

        /**
         * Sets the flashType.
         *
         * <p>If not set, the flash type will default to {@link #FLASH_TYPE_ONE_SHOT_FLASH}.
         *
         * @param flashType The requested flash mode. Value is {@link #FLASH_TYPE_ONE_SHOT_FLASH}
         *                 or {@link #FLASH_TYPE_USE_TORCH_AS_FLASH}.
         * @return The current Builder.
         * @hide
         */
        @NonNull
        @RestrictTo(Scope.LIBRARY_GROUP)
        public Builder setFlashType(@FlashType int flashType) {
            getMutableConfig().insertOption(OPTION_FLASH_TYPE, flashType);
            return this;
        }

        /**
         * Sets the output JPEG image compression quality.
         *
         * <p>This is used for the {@link ImageProxy} which is returned by
         * {@link #takePicture(Executor, OnImageCapturedCallback)} or the output JPEG image which
         * is saved by {@link #takePicture(OutputFileOptions, Executor, OnImageSavedCallback)}.
         * The saved JPEG image might be cropped according to the {@link ViewPort} setting or
         * the crop aspect ratio set by {@link #setCropAspectRatio(Rational)}. The JPEG quality
         * setting will also be used to compress the cropped output image.
         *
         * <p>If not set, a default value will be used according to the capture mode setting.
         * JPEG compression quality 95 is used for {@link #CAPTURE_MODE_MINIMIZE_LATENCY} and 100
         * is used for {@link #CAPTURE_MODE_MAXIMIZE_QUALITY}.
         *
         * @param jpegQuality The requested output JPEG image compression quality. The value must
         *                   be in range [1..100] which larger is higher quality.
         * @return The current Builder.
         * @throws IllegalArgumentException if the input value is not in range [1..100].
         */
        @NonNull
        public Builder setJpegQuality(@IntRange(from = 1, to = 100) int jpegQuality) {
            Preconditions.checkArgumentInRange(jpegQuality, 1, 100, "jpegQuality");
            getMutableConfig().insertOption(OPTION_JPEG_COMPRESSION_QUALITY,
                    jpegQuality);
            return this;
        }

        // Implementations of IoConfig.Builder default methods

        /**
         * Sets the default executor that will be used for IO tasks.
         *
         * <p> This executor will be used for any IO tasks specifically for ImageCapture, such as
         * {@link ImageCapture#takePicture(OutputFileOptions, Executor,
         * ImageCapture.OnImageSavedCallback)}. If no executor is set, then a default Executor
         * specifically for IO will be used instead.
         *
         * @param executor The executor which will be used for IO tasks.
         * @return the current Builder.
         */
        @Override
        @NonNull
        public Builder setIoExecutor(@NonNull Executor executor) {
            getMutableConfig().insertOption(OPTION_IO_EXECUTOR, executor);
            return this;
        }

        // Implementations of UseCaseConfig.Builder default methods

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setDefaultSessionConfig(@NonNull SessionConfig sessionConfig) {
            getMutableConfig().insertOption(OPTION_DEFAULT_SESSION_CONFIG, sessionConfig);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setDefaultCaptureConfig(@NonNull CaptureConfig captureConfig) {
            getMutableConfig().insertOption(OPTION_DEFAULT_CAPTURE_CONFIG, captureConfig);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setSessionOptionUnpacker(
                @NonNull SessionConfig.OptionUnpacker optionUnpacker) {
            getMutableConfig().insertOption(OPTION_SESSION_CONFIG_UNPACKER, optionUnpacker);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setCaptureOptionUnpacker(
                @NonNull CaptureConfig.OptionUnpacker optionUnpacker) {
            getMutableConfig().insertOption(OPTION_CAPTURE_CONFIG_UNPACKER, optionUnpacker);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setCameraSelector(@NonNull CameraSelector cameraSelector) {
            getMutableConfig().insertOption(OPTION_CAMERA_SELECTOR, cameraSelector);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setSurfaceOccupancyPriority(int priority) {
            getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
            return this;
        }

        /** @hide */
        @RestrictTo(Scope.LIBRARY_GROUP)
        @Override
        @NonNull
        public Builder setUseCaseEventCallback(
                @NonNull UseCase.EventCallback useCaseEventCallback) {
            getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_CALLBACK, useCaseEventCallback);
            return this;
        }
    }
}