ViewfinderSurfaceRequest.kt

/*
 * Copyright 2023 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.viewfinder.surface

import android.annotation.SuppressLint
import android.hardware.camera2.CameraMetadata
import android.util.Size
import android.view.Surface
import androidx.annotation.IntDef
import androidx.annotation.RestrictTo
import androidx.camera.impl.utils.Logger
import androidx.camera.impl.utils.executor.CameraExecutors
import androidx.camera.impl.utils.futures.FutureCallback
import androidx.camera.impl.utils.futures.Futures
import androidx.camera.viewfinder.impl.surface.DeferredSurface
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Consumer
import androidx.core.util.Preconditions
import com.google.auto.value.AutoValue
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.CancellationException
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicReference

/**
 * The request to get a [Surface] to display camera feed.
 *
 *
 * This request contains requirements for the surface resolution and camera
 * device information from [CameraCharacteristics].
 *
 *  Calling [ViewfinderSurfaceRequest.markSurfaceSafeToRelease] will notify the
 * surface provider that the surface is not needed and related resources can be released.
 *
 * Creates a new surface request with surface resolution, camera device, lens facing and
 * sensor orientation information.
 *
 * @param resolution The requested surface resolution. It is the output surface size
 *                   the camera is configured with, instead of {@link CameraViewfinder}
 *                   view size.
 * @param lensFacing The camera lens facing.
 * @param sensorOrientation THe camera sensor orientation.
 * @param implementationMode The {@link ImplementationMode} to apply to the viewfinder.
 */
class ViewfinderSurfaceRequest internal constructor(
    val resolution: Size,
    @LensFacingValue val lensFacing: Int,
    @SensorOrientationDegreesValue val sensorOrientation: Int,
    val implementationMode: ImplementationMode?
) {
    private val mInternalDeferredSurface: DeferredSurface
    private val cancellationCompleter: CallbackToFutureAdapter.Completer<Void?>
    private val sessionStatusFuture: ListenableFuture<Void?>
    private val surfaceCompleter: CallbackToFutureAdapter.Completer<Surface>

    private val surfaceFutureAsync: ListenableFuture<Surface>

    init {
        // To ensure concurrency and ordering, operations are chained. Completion can only be
        // triggered externally by the top-level completer (mSurfaceCompleter). The other future
        // completers are only completed by callbacks set up within the constructor of this class
        // to ensure correct ordering of events.

        // Cancellation listener must be called last to ensure the result can be retrieved from
        // the session listener.
        val surfaceRequestString =
            "SurfaceRequest[size: " + resolution + ", id: " + this.hashCode() + "]"
        val cancellationCompleterRef =
            AtomicReference<CallbackToFutureAdapter.Completer<Void?>?>(null)
        val requestCancellationFuture =
            CallbackToFutureAdapter.getFuture {
                cancellationCompleterRef.set(it)
                "$surfaceRequestString-cancellation"
            }
        val requestCancellationCompleter =
            Preconditions.checkNotNull(cancellationCompleterRef.get())
        cancellationCompleter = requestCancellationCompleter

        // Surface session status future completes and is responsible for finishing the
        // cancellation listener.
        val sessionStatusCompleterRef =
            AtomicReference<CallbackToFutureAdapter.Completer<Void?>?>(null)
        sessionStatusFuture =
            CallbackToFutureAdapter.getFuture<Void?> {
                sessionStatusCompleterRef.set(it)
                "$surfaceRequestString-status"
            }
        Futures.addCallback<Void?>(sessionStatusFuture, object : FutureCallback<Void?> {
            override fun onSuccess(result: Void?) {
                // Cancellation didn't occur, so complete the cancellation future. There
                // shouldn't ever be any standard listeners on this future, so nothing should be
                // invoked.
                Preconditions.checkState(requestCancellationCompleter.set(null))
            }

            override fun onFailure(t: Throwable) {
                if (t is RequestCancelledException) {
                    // Cancellation occurred. Notify listeners.
                    Preconditions.checkState(
                        requestCancellationFuture.cancel(false)
                    )
                } else {
                    // Cancellation didn't occur, complete the future so cancellation listeners
                    // are not invoked.
                    Preconditions.checkState(requestCancellationCompleter.set(null))
                }
            }
        }, CameraExecutors.directExecutor())

        // Create the surface future/completer. This will be used to complete the rest of the
        // future chain and can be set externally via SurfaceRequest methods.
        val sessionStatusCompleter = Preconditions.checkNotNull(sessionStatusCompleterRef.get())
        val surfaceCompleterRef =
            AtomicReference<CallbackToFutureAdapter.Completer<Surface>?>(null)
        surfaceFutureAsync =
            CallbackToFutureAdapter.getFuture {
                surfaceCompleterRef.set(it)
                "$surfaceRequestString-Surface"
            }
        surfaceCompleter = Preconditions.checkNotNull(surfaceCompleterRef.get())

        // Create the viewfinder surface which will be used for communicating when the
        // camera and consumer are done using the surface. Note this anonymous inner class holds
        // an implicit reference to the ViewfinderSurfaceRequest. This is by design, and ensures the
        // ViewfinderSurfaceRequest and all contained future completers will not be garbage
        // collected as long as the ViewfinderSurface is referenced externally (via
        // getViewfinderSurface()).
        mInternalDeferredSurface =
            object : DeferredSurface() {
                override fun provideSurfaceAsync(): ListenableFuture<Surface> {
                    Logger.d(
                        TAG,
                        "mInternalViewfinderSurface + $this provideSurface"
                    )
                    return surfaceFutureAsync
                }
            }
        val terminationFuture: ListenableFuture<Void?> =
            mInternalDeferredSurface.getTerminationFutureAsync()

        // Propagate surface completion to the session future.
        Futures.addCallback<Surface>(surfaceFutureAsync, object : FutureCallback<Surface?> {
            override fun onSuccess(result: Surface?) {
                // On successful setting of a surface, defer completion of the session future to
                // the ViewfinderSurface termination future. Once that future completes, then it
                // is safe to release the Surface and associated resources.
                Futures.propagate(terminationFuture, sessionStatusCompleter)
            }

            override fun onFailure(t: Throwable) {
                // Translate cancellation into a SurfaceRequestCancelledException. Other
                // exceptions mean either the request was completed via willNotProvideSurface() or a
                // programming error occurred. In either case, the user will never see the
                // session future (an immediate future will be returned instead), so complete the
                // future so cancellation listeners are never called.
                if (t is CancellationException) {
                    Preconditions.checkState(
                        sessionStatusCompleter.setException(
                            RequestCancelledException(
                                "$surfaceRequestString cancelled.", t
                            )
                        )
                    )
                } else {
                    sessionStatusCompleter.set(null)
                }
            }
        }, CameraExecutors.directExecutor())

        // If the viewfinder surface is terminated, there are two cases:
        // 1. The surface has not yet been provided to the camera (or marked as 'will not
        //    complete'). Treat this as if the surface request has been cancelled.
        // 2. The surface was already provided to the camera. In this case the camera is now
        //    finished with the surface, so cancelling the surface future below will be a no-op.
        terminationFuture.addListener({
            Logger.d(
                TAG,
                ("mInternalViewfinderSurface + " + mInternalDeferredSurface + " " +
                    "terminateFuture triggered")
            )
            surfaceFutureAsync.cancel(true)
        }, CameraExecutors.directExecutor())
    }

    /**
     * Closes the viewfinder surface to mark it as safe to release.
     *
     *
     *  This method should be called by the user when the requested surface is not needed and
     * related resources can be released.
     */
    fun markSurfaceSafeToRelease() {
        mInternalDeferredSurface.close()
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun getSurface(): DeferredSurface {
        return mInternalDeferredSurface
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @SuppressLint("PairedRegistration")
    fun addRequestCancellationListener(
        executor: Executor,
        listener: Runnable
    ) {
        cancellationCompleter.addCancellationListener(listener, executor)
    }

    /**
     * Completes the request for a [Surface] if it has not already been
     * completed or cancelled.
     *
     *
     * Once the camera no longer needs the provided surface, the `resultListener` will be
     * invoked with a [Result] containing [Result.RESULT_SURFACE_USED_SUCCESSFULLY].
     * At this point it is safe to release the surface and any underlying resources. Releasing
     * the surface before receiving this signal may cause undesired behavior on lower API levels.
     *
     *
     * If the request is cancelled by the camera before successfully attaching the
     * provided surface to the camera, then the `resultListener` will be invoked with a
     * [Result] containing [Result.RESULT_REQUEST_CANCELLED].
     *
     *
     * If the request was previously completed via [.willNotProvideSurface], then
     * `resultListener` will be invoked with a [Result] containing
     * [Result.RESULT_WILL_NOT_PROVIDE_SURFACE].
     *
     *
     * Upon returning from this method, the surface request is guaranteed to be complete.
     * However, only the `resultListener` provided to the first invocation of this method
     * should be used to track when the provided [Surface] is no longer in use by the
     * camera, as subsequent invocations will always invoke the `resultListener` with a
     * [Result] containing [Result.RESULT_SURFACE_ALREADY_PROVIDED].
     *
     * @param surface        The surface which will complete the request.
     * @param executor       Executor used to execute the `resultListener`.
     * @param resultListener Listener used to track how the surface is used by the camera in
     * response to being provided by this method.
     */
    fun provideSurface(
        surface: Surface,
        executor: Executor,
        resultListener: Consumer<Result?>
    ) {
        if (surfaceCompleter.set(surface) || surfaceFutureAsync.isCancelled) {
            // Session will be pending completion (or surface request was cancelled). Return the
            // session future.
            Futures.addCallback(sessionStatusFuture, object : FutureCallback<Void?> {
                override fun onSuccess(result: Void?) {
                    resultListener.accept(
                        Result(
                            Result.RESULT_SURFACE_USED_SUCCESSFULLY,
                            surface
                        )
                    )
                }

                override fun onFailure(t: Throwable) {
                    Preconditions.checkState(
                        t is RequestCancelledException, ("Camera " +
                            "surface session should only fail with request " +
                            "cancellation. Instead failed due to:\n" + t)
                    )
                    resultListener.accept(Result(Result.RESULT_REQUEST_CANCELLED, surface))
                }
            }, executor)
        } else {
            // Surface request is already complete
            Preconditions.checkState(surfaceFutureAsync.isDone)
            try {
                surfaceFutureAsync.get()
                // Getting this far means the surface was already provided.
                executor.execute {
                    resultListener.accept(
                        Result(
                            Result.RESULT_SURFACE_ALREADY_PROVIDED,
                            surface
                        )
                    )
                }
            } catch (e: InterruptedException) {
                executor.execute {
                    resultListener.accept(
                        Result(
                            Result.RESULT_WILL_NOT_PROVIDE_SURFACE,
                            surface
                        )
                    )
                }
            } catch (e: ExecutionException) {
                executor.execute {
                    resultListener.accept(
                        Result(
                            Result.RESULT_WILL_NOT_PROVIDE_SURFACE,
                            surface
                        )
                    )
                }
            }
        }
    }

    /**
     * Signals that the request will never be fulfilled.
     *
     *
     * This may be called in the case where the application may be shutting down and a
     * surface will never be produced to fulfill the request.
     *
     *
     * This will be called by CameraViewfinder as soon as it is known that the request will not
     * be fulfilled. Failure to complete the SurfaceRequest via `willNotProvideSurface()`
     * or [.provideSurface] may cause long delays in shutting
     * down the camera.
     *
     *
     * Upon returning from this method, the request is guaranteed to be complete, regardless
     * of the return value. If the request was previously successfully completed by
     * [.provideSurface], invoking this method will return
     * `false`, and will have no effect on how the surface is used by the camera.
     *
     * @return `true` if this call to `willNotProvideSurface()` successfully
     * completes the request, i.e., the request has not already been completed via
     * [.provideSurface] or by a previous call to
     * `willNotProvideSurface()` and has not already been cancelled by the camera.
     */
    fun willNotProvideSurface(): Boolean {
        return surfaceCompleter.setException(
            DeferredSurface.SurfaceUnavailableException(
                ("Surface request will not complete.")
            )
        )
    }

    /**
     * Builder for [ViewfinderSurfaceRequest].
     */
    class Builder {
        private val resolution: Size

        @LensFacingValue
        private var lensFacing = CameraMetadata.LENS_FACING_BACK

        @SensorOrientationDegreesValue
        private var sensorOrientation = 0
        private var implementationMode: ImplementationMode? = null

        /**
         * Constructor for [Builder].
         *
         *
         * Creates a builder with viewfinder resolution.
         *
         * @param resolution viewfinder resolution.
         */
        constructor(resolution: Size) {
            this.resolution = resolution
        }

        /**
         * Constructor for [Builder].
         *
         *
         * Creates a builder with other builder instance. The returned builder will be
         * pre-populated with the state of the provided builder.
         *
         * @param builder [Builder] instance.
         */
        constructor(builder: Builder) {
            resolution = builder.resolution
            implementationMode = builder.implementationMode
            lensFacing = builder.lensFacing
            sensorOrientation = builder.sensorOrientation
        }

        /**
         * Constructor for [Builder].
         *
         *
         * Creates a builder with other [ViewfinderSurfaceRequest] instance. The
         * returned builder will be pre-populated with the state of the provided
         * [ViewfinderSurfaceRequest] instance.
         *
         * @param surfaceRequest [ViewfinderSurfaceRequest] instance.
         */
        constructor(surfaceRequest: ViewfinderSurfaceRequest) {
            resolution = surfaceRequest.resolution
            implementationMode = surfaceRequest.implementationMode
            lensFacing = surfaceRequest.lensFacing
            sensorOrientation = surfaceRequest.sensorOrientation
        }

        /**
         * Sets the [ImplementationMode].
         *
         *
         * **Possible values:**
         *
         *  * [PERFORMANCE][ImplementationMode.PERFORMANCE]
         *  * [COMPATIBLE][ImplementationMode.COMPATIBLE]
         *
         * @param implementationMode The [ImplementationMode].
         * @return This builder.
         */
        fun setImplementationMode(implementationMode: ImplementationMode?): Builder {
            this.implementationMode = implementationMode
            return this
        }

        /**
         * Sets the lens facing.
         *
         *
         * **Possible values:**
         *
         *  * [FRONT][CameraMetadata.LENS_FACING_FRONT]
         *  * [BACK][CameraMetadata.LENS_FACING_BACK]
         *  * [EXTERNAL][CameraMetadata.LENS_FACING_EXTERNAL]
         *
         *
         *
         * The value can be retrieved from [CameraCharacteristics] by key
         * [CameraCharacteristics.LENS_FACING]. If not set,
         * [CameraMetadata.LENS_FACING_BACK] will be used by default.
         *
         * @param lensFacing The lens facing.
         * @return This builder.
         */
        fun setLensFacing(@LensFacingValue lensFacing: Int): Builder {
            this.lensFacing = lensFacing
            return this
        }

        /**
         * Sets the sensor orientation.
         *
         *
         * **Range of valid values:**<br></br>
         * 0, 90, 180, 270
         *
         *
         * The value can be retrieved from [CameraCharacteristics] by key
         * [CameraCharacteristics.SENSOR_ORIENTATION]. If it is not
         * set, 0 will be used by default.
         *
         * @param sensorOrientation
         * @return this builder.
         */
        fun setSensorOrientation(@SensorOrientationDegreesValue sensorOrientation: Int): Builder {
            this.sensorOrientation = sensorOrientation
            return this
        }

        /**
         * Builds the [ViewfinderSurfaceRequest].
         * @return the instance of [ViewfinderSurfaceRequest].
         *
         * @throws IllegalArgumentException
         */
        fun build(): ViewfinderSurfaceRequest {
            if ((lensFacing != CameraMetadata.LENS_FACING_FRONT
                    ) && (lensFacing != CameraMetadata.LENS_FACING_BACK
                    ) && (lensFacing != CameraMetadata.LENS_FACING_EXTERNAL)
            ) {
                throw IllegalArgumentException(
                    ("Lens facing value: $lensFacing is invalid")
                )
            }
            if ((sensorOrientation != 0
                    ) && (sensorOrientation != 90
                    ) && (sensorOrientation != 180
                    ) && (sensorOrientation != 270)
            ) {
                throw IllegalArgumentException(
                    ("Sensor orientation value: $sensorOrientation is invalid")
                )
            }
            return ViewfinderSurfaceRequest(
                resolution,
                lensFacing,
                sensorOrientation,
                implementationMode
            )
        }
    }

    internal class RequestCancelledException(message: String, cause: Throwable) :
        RuntimeException(message, cause)

    /**
     * Result of providing a surface to a [ViewfinderSurfaceRequest] via
     * [.provideSurface].
     *
     * Can be used to compare to results returned to `resultListener` in
     * [.provideSurface].
     *
     * @param code    One of [.RESULT_SURFACE_USED_SUCCESSFULLY],
     * [.RESULT_REQUEST_CANCELLED], [.RESULT_INVALID_SURFACE],
     * [.RESULT_SURFACE_ALREADY_PROVIDED], or
     * [.RESULT_WILL_NOT_PROVIDE_SURFACE].
     * @param surface The [Surface] used to complete the [ViewfinderSurfaceRequest].
     */
    @AutoValue
    data class Result(@ResultCode val code: Int, val surface: Surface) {
        /**
         * Possible result codes.
         *
         */
        @IntDef(
            RESULT_SURFACE_USED_SUCCESSFULLY,
            RESULT_REQUEST_CANCELLED,
            RESULT_INVALID_SURFACE,
            RESULT_SURFACE_ALREADY_PROVIDED,
            RESULT_WILL_NOT_PROVIDE_SURFACE
        )
        @Retention(
            AnnotationRetention.SOURCE
        )
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        annotation class ResultCode()
        companion object {
            /**
             * Provided surface was successfully used by the camera and eventually detached once no
             * longer needed by the camera.
             *
             *
             * This result denotes that it is safe to release the [Surface] and any underlying
             * resources.
             *
             *
             * For compatibility reasons, the [Surface] object should not be reused by
             * future [SurfaceRequests][ViewfinderSurfaceRequest], and a new surface should be
             * created instead.
             */
            const val RESULT_SURFACE_USED_SUCCESSFULLY = 0

            /**
             * Provided surface was never attached to the camera due to the
             * [ViewfinderSurfaceRequest] being cancelled by the camera.
             *
             *
             * It is safe to release or reuse [Surface], assuming it was not previously
             * attached to a camera via [.provideSurface]. If
             * reusing the surface for a future surface request, it should be verified that the
             * surface still matches the resolution specified by
             * [ViewfinderSurfaceRequest.resolution].
             */
            const val RESULT_REQUEST_CANCELLED = 1

            /**
             * Provided surface could not be used by the camera.
             *
             *
             * This is likely due to the [Surface] being closed prematurely or the resolution
             * of the surface not matching the resolution specified by
             * [ViewfinderSurfaceRequest.resolution].
             */
            const val RESULT_INVALID_SURFACE = 2

            /**
             * Surface was not attached to the camera through this invocation of
             * [.provideSurface] due to the
             * [ViewfinderSurfaceRequest] already being complete with a surface.
             *
             *
             * The [ViewfinderSurfaceRequest] has already been completed by a previous
             * invocation of [.provideSurface].
             *
             *
             * It is safe to release or reuse the [Surface], assuming it was not previously
             * attached to a camera via [.provideSurface].
             */
            const val RESULT_SURFACE_ALREADY_PROVIDED = 3

            /**
             * Surface was not attached to the camera through this invocation of
             * [.provideSurface] due to the
             * [ViewfinderSurfaceRequest] already being marked as "will not provide surface".
             *
             *
             * The [ViewfinderSurfaceRequest] has already been marked as 'will not provide
             * surface' by a previous invocation of [.willNotProvideSurface].
             *
             *
             * It is safe to release or reuse the [Surface], assuming it was not previously
             * attached to a camera via [.provideSurface].
             */
            const val RESULT_WILL_NOT_PROVIDE_SURFACE = 4
        }
    }

    /**
     * Valid integer sensor orientation degrees values.
     */
    @IntDef(0, 90, 180, 270)
    @Retention(AnnotationRetention.SOURCE)
    internal annotation class SensorOrientationDegreesValue()

    /**
     * Valid integer sensor orientation degrees values.
     */
    @IntDef(
        CameraMetadata.LENS_FACING_FRONT,
        CameraMetadata.LENS_FACING_BACK,
        CameraMetadata.LENS_FACING_EXTERNAL
    )
    @Retention(
        AnnotationRetention.SOURCE
    )
    internal annotation class LensFacingValue()
    companion object {
        private const val TAG = "SurfaceRequest"
    }
}