CameraController.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.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;
import android.util.Size;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalUseCaseGroup;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
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.lifecycle.ProcessCameraProvider;

/**
 * The abstract base camera controller class.
 *
 * <p> The controller is a high level API manages the entire CameraX stack. This base class is
 * responsible for 1) initializing camera stack and 2) creating use cases based on user inputs.
 * Subclass this class to bind the use cases to camera.
 */
abstract class CameraController {

    private static final String TAG = "CameraController";

    // TODO(b/148791439): Temporary. Remove once camera selection is implemented.
    static final CameraSelector CAMERA_SELECTOR = CameraSelector.DEFAULT_BACK_CAMERA;

    // Synthetic access
    @SuppressWarnings("WeakerAccess")
    // CameraController and PreviewView hold reference to each other. The 2-way link is managed
    // by PreviewView.
    @Nullable
    Preview mPreview;

    // Size of the PreviewView. Used for creating ViewPort.
    @Nullable
    private Size mPreviewSize;

    // The latest bound camera.
    @Nullable
    Camera mCamera;

    // Synthetic access
    @SuppressWarnings("WeakerAccess")
    @Nullable
    ProcessCameraProvider mCameraProvider;

    CameraController(@NonNull Context context) {
        // Wait for camera to be initialized before binding use cases.
        Futures.addCallback(
                ProcessCameraProvider.getInstance(context),
                new FutureCallback<ProcessCameraProvider>() {

                    @SuppressLint("MissingPermission")
                    @Override
                    public void onSuccess(@Nullable ProcessCameraProvider provider) {
                        mCameraProvider = provider;
                        mCamera = startCamera();
                    }

                    @Override
                    public void onFailure(Throwable t) {
                        // TODO(b/148791439): fail gracefully and notify caller.
                        throw new RuntimeException("CameraX failed to initialize.", t);
                    }

                }, CameraXExecutors.mainThreadExecutor());
    }

    /**
     * Implemented by children to refresh after {@link UseCase} is changed.
     */
    @Nullable
    abstract Camera startCamera();

    // Preview use case.

    /**
     * Internal API used by {@link PreviewView} notify changes.
     *
     * TODO(b/148791439): add LayoutDirection
     */
    @SuppressLint("MissingPermission")
    @MainThread
    void attachPreviewSurface(Preview.SurfaceProvider surfaceProvider, int width, int height) {
        Threads.checkMainThread();
        if (width == 0 || height == 0) {
            return;
        }
        Size newPreviewSize = new Size(width, height);
        if (newPreviewSize.equals(mPreviewSize) && mPreview != null) {
            // If the Surface size hasn't changed, reuse the UseCase with the new SurfaceProvider.
            mPreview.setSurfaceProvider(surfaceProvider);
            return;
        }
        if (mPreview != null && mCameraProvider != null) {
            mCameraProvider.unbind(mPreview);
        }
        mPreview = createPreview(surfaceProvider, newPreviewSize);
        mPreviewSize = newPreviewSize;
        mCamera = startCamera();
    }

    /**
     * Clear {@link PreviewView} to remove the UI reference.
     */
    @MainThread
    void clearPreviewSurface() {
        Threads.checkMainThread();
        if (mCameraProvider != null) {
            // Preview is required. Unbind everything if Preview is down.
            mCameraProvider.unbindAll();
        }
        mPreviewSize = null;
        mPreview = null;
        mCamera = null;
    }

    @MainThread
    Preview createPreview(Preview.SurfaceProvider surfaceProvider, Size previewSize) {
        Threads.checkMainThread();
        Preview preview = new Preview.Builder()
                .setTargetResolution(previewSize)
                .build();
        preview.setSurfaceProvider(surfaceProvider);
        return preview;
    }

    // TODO(b/148791439): Support ImageCapture.

    // TODO(b/148791439): Support VideoCapture as @Experimental.

    // TODO(b/148791439): Allow user to select camera.

    // TODO(b/148791439): Handle rotation so the output is always in gravity orientation.

    // TODO(b/148791439): Support CameraControl

    /**
     * Creates {@link UseCaseGroup} from all the use cases.
     *
     * <p> Preview is required. If it is null, then controller is not ready. Return null and ignore
     * other use cases.
     */
    @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
    protected UseCaseGroup createUseCaseGroup() {
        UseCaseGroup.Builder builder = new UseCaseGroup.Builder();
        if (mPreview == null) {
            Log.d(TAG, "PreviewView is not ready.");
            return null;
        }
        builder.addUseCase(mPreview);

        // TODO(b/148791439): add all the use cases.
        // TODO(b/148791439): set ViewPort if mPreviewSize/ LayoutDirection is not null.
        return builder.build();
    }
}