CaptureSessionRepository.java

/*
 * Copyright 2020 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.camera2.internal;

import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.core.impl.DeferrableSurface;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * The repository to maintain a list of the created and releasing SynchronizedCaptureSession.
 *
 * <p> The repository also help to close the created SynchronizedCaptureSession when the camera is
 * disconnected.
 */
class CaptureSessionRepository {
    /** Executor for all the callbacks from the {@link CameraCaptureSession}. */
    @NonNull
    @CameraExecutor
    final Executor mExecutor;

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

    @GuardedBy("mLock")
    final Set<SynchronizedCaptureSession> mCaptureSessions = new LinkedHashSet<>();
    @GuardedBy("mLock")
    final Set<SynchronizedCaptureSession> mClosingCaptureSession = new LinkedHashSet<>();
    @GuardedBy("mLock")
    final Set<SynchronizedCaptureSession> mCreatingCaptureSessions = new LinkedHashSet<>();
    @GuardedBy("mLock")
    final Map<SynchronizedCaptureSession, List<DeferrableSurface>> mDeferrableSurfaceMap =
            new HashMap<>();

    CaptureSessionRepository(@NonNull @CameraExecutor Executor executor) {
        mExecutor = executor;
    }

    private final CameraDevice.StateCallback mCameraStateCallback =
            new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    // Nothing to do.
                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    // Force close all opened CameraCaptureSessions since the CameraDevice is in
                    // error state. The CameraCaptureSession.close() may not invoke the onClosed()
                    // callback so it has to finish the close process forcibly.
                    forceOnClosedCaptureSessions();
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    // Force the onClosed() callback to be made. This is necessary because the
                    // onClosed() callback never gets called if CameraDevice.StateCallback
                    // .onDisconnected() is called. See
                    // TODO(b/140955560) If the issue is fixed then on OS releases with the fix
                    //  this should not be called and instead onClosed() should be called by the
                    //  framework instead.

                    // Force close all opened CameraCaptureSessions since the CameraDevice is
                    // disconnected.
                    // The CameraCaptureSession will call its close() automatically once the
                    // onDisconnected callback is invoked.
                    forceOnClosedCaptureSessions();
                }

                private void forceOnClosedCaptureSessions() {
                    mExecutor.execute(() -> {
                        LinkedHashSet<SynchronizedCaptureSession> sessions = new LinkedHashSet<>();
                        synchronized (mLock) {
                            sessions.addAll(new LinkedHashSet<>(mCreatingCaptureSessions));
                            sessions.addAll(new LinkedHashSet<>(mCaptureSessions));
                        }
                        forceOnClosed(sessions);
                    });

                }
            };

    @NonNull
    CameraDevice.StateCallback getCameraStateCallback() {
        return mCameraStateCallback;
    }

    static void forceOnClosed(@NonNull Set<SynchronizedCaptureSession> sessions) {
        for (SynchronizedCaptureSession session : sessions) {
            session.getStateCallback().onClosed(session);
        }
    }

    @NonNull
    List<SynchronizedCaptureSession> getCaptureSessions() {
        synchronized (mLock) {
            return new ArrayList<>(mCaptureSessions);
        }
    }

    @NonNull
    List<SynchronizedCaptureSession> getClosingCaptureSession() {
        synchronized (mLock) {
            return new ArrayList<>(mClosingCaptureSession);
        }
    }

    @NonNull
    List<SynchronizedCaptureSession> getCreatingCaptureSessions() {
        synchronized (mLock) {
            return new ArrayList<>(mCreatingCaptureSessions);
        }
    }

    /**
     * To register a DeferrableSurface list that is using by the SynchronizedCaptureSession.
     *
     *  <p>To register the deferrableSurface list means to identifying the list of
     *  deferrableSurfaces is already occupied by the SynchronizedCaptureSession.The
     *  registration information is shared between SynchronizedCaptureSession. Every
     *  SynchronizedCaptureSessions can get the registered information to know if a
     *  DeferrableSurface is already occupied by the another SynchronizedCaptureSession.
     *
     * @param synchronizedCaptureSession The SynchronizedCaptureSession that is going to configure
     *                                   a DeferrableSurfaceList.
     * @param deferrableSurfaces         The deferrable surface list that is configured to the
     *                                   SynchronizedCaptureSession.
     * @return a map of all the registered SynchronizedCaptureSession to the DeferrableSurfaceList.
     */
    Map<SynchronizedCaptureSession, List<DeferrableSurface>> registerDeferrableSurface(
            @NonNull SynchronizedCaptureSession synchronizedCaptureSession,
            @NonNull List<DeferrableSurface> deferrableSurfaces) {
        synchronized (mLock) {
            mDeferrableSurfaceMap.put(synchronizedCaptureSession, deferrableSurfaces);
            return new HashMap<>(mDeferrableSurfaceMap);
        }
    }

    /**
     * Unregister the {@link SynchronizedCaptureSession} you previously registered using
     * {@link #registerDeferrableSurface}.
     *
     * @param synchronizedCaptureSession the SynchronizedCaptureSession to be removed from the list.
     */
    void unregisterDeferrableSurface(
            @NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mDeferrableSurfaceMap.remove(synchronizedCaptureSession);
        }
    }

    void onCreateCaptureSession(@NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mCreatingCaptureSessions.add(synchronizedCaptureSession);
        }
    }

    void onCaptureSessionConfigureFail(
            @NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mCreatingCaptureSessions.remove(synchronizedCaptureSession);
        }
    }

    void onCaptureSessionCreated(
            @NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mCaptureSessions.add(synchronizedCaptureSession);
            mCreatingCaptureSessions.remove(synchronizedCaptureSession);
        }
    }

    void onCaptureSessionClosed(@NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mCaptureSessions.remove(synchronizedCaptureSession);
            mClosingCaptureSession.remove(synchronizedCaptureSession);
        }
    }

    void onCaptureSessionClosing(@NonNull SynchronizedCaptureSession synchronizedCaptureSession) {
        synchronized (mLock) {
            mClosingCaptureSession.add(synchronizedCaptureSession);
        }
    }
}