public final class

Camera2CameraInfoImpl

extends java.lang.Object

implements CameraInfoInternal

 java.lang.Object

↳androidx.camera.camera2.internal.Camera2CameraInfoImpl

Gradle dependencies

compile group: 'androidx.camera', name: 'camera-camera2', version: '1.2.0-alpha01'

  • groupId: androidx.camera
  • artifactId: camera-camera2
  • version: 1.2.0-alpha01

Artifact androidx.camera:camera-camera2:1.2.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Implementation of the CameraInfoInternal interface that exposes parameters through camera2.

Construction consists of two stages. The constructor creates a implementation without a Camera2CameraControlImpl and will return default values for camera control related states like zoom/exposure/torch. After Camera2CameraInfoImpl.linkWithCameraControl(Camera2CameraControlImpl) is called, zoom/exposure/torch API will reflect the states in the Camera2CameraControlImpl. Any CameraCaptureCallbacks added before this link will also be added to the Camera2CameraControlImpl.

Summary

Methods
public voidaddSessionCaptureCallback(java.util.concurrent.Executor executor, CameraCaptureCallback callback)

public CamcorderProfileProvidergetCamcorderProfileProvider()

public Camera2CameraInfogetCamera2CameraInfo()

Gets the implementation of Camera2CameraInfo.

public CameraCharacteristicsCompatgetCameraCharacteristicsCompat()

public java.util.Map<java.lang.String, CameraCharacteristics>getCameraCharacteristicsMap()

Returns a map consisting of the camera ids and the CameraCharacteristicss.

public java.lang.StringgetCameraId()

public QuirksgetCameraQuirks()

public LiveData<CameraState>getCameraState()

public ExposureStategetExposureState()

public java.lang.StringgetImplementationType()

public java.lang.IntegergetLensFacing()

public intgetSensorRotationDegrees()

public intgetSensorRotationDegrees(int relativeRotation)

public LiveData<java.lang.Integer>getTorchState()

public LiveData<ZoomState>getZoomState()

public booleanhasFlashUnit()

public booleanisFocusMeteringSupported(FocusMeteringAction action)

public booleanisPrivateReprocessingSupported()

public booleanisYuvReprocessingSupported()

public voidremoveSessionCaptureCallback(CameraCaptureCallback callback)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public java.lang.String getCameraId()

public CameraCharacteristicsCompat getCameraCharacteristicsCompat()

public java.lang.Integer getLensFacing()

public int getSensorRotationDegrees(int relativeRotation)

public int getSensorRotationDegrees()

public boolean hasFlashUnit()

public LiveData<java.lang.Integer> getTorchState()

public LiveData<ZoomState> getZoomState()

public ExposureState getExposureState()

public LiveData<CameraState> getCameraState()

public java.lang.String getImplementationType()

When the CameraX configuration is Camera2Config, the return value depends on whether the device is legacy (CameraCharacteristics == ).

Returns:

CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY if the device is legacy, otherwise CameraInfo.IMPLEMENTATION_TYPE_CAMERA2.

public boolean isFocusMeteringSupported(FocusMeteringAction action)

public boolean isYuvReprocessingSupported()

public boolean isPrivateReprocessingSupported()

public CamcorderProfileProvider getCamcorderProfileProvider()

public void addSessionCaptureCallback(java.util.concurrent.Executor executor, CameraCaptureCallback callback)

public void removeSessionCaptureCallback(CameraCaptureCallback callback)

public Quirks getCameraQuirks()

public Camera2CameraInfo getCamera2CameraInfo()

Gets the implementation of Camera2CameraInfo.

public java.util.Map<java.lang.String, CameraCharacteristics> getCameraCharacteristicsMap()

Returns a map consisting of the camera ids and the CameraCharacteristicss.

For every camera, the map contains at least the CameraCharacteristics for the camera id. If the camera is logical camera, it will also contain associated physical camera ids and their CameraCharacteristics.

Source

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

import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING;

import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
import android.util.Pair;
import android.view.Surface;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
import androidx.camera.camera2.internal.compat.workaround.FlashAvailabilityChecker;
import androidx.camera.camera2.interop.Camera2CameraInfo;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraState;
import androidx.camera.core.ExposureState;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.Logger;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.CamcorderProfileProvider;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
import androidx.camera.core.impl.Quirks;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.core.util.Preconditions;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.Observer;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Implementation of the {@link CameraInfoInternal} interface that exposes parameters through
 * camera2.
 *
 * <p>Construction consists of two stages. The constructor creates a implementation without a
 * {@link Camera2CameraControlImpl} and will return default values for camera control related
 * states like zoom/exposure/torch. After {@link #linkWithCameraControl} is called,
 * zoom/exposure/torch API will reflect the states in the {@link Camera2CameraControlImpl}. Any
 * CameraCaptureCallbacks added before this link will also be added
 * to the {@link Camera2CameraControlImpl}.
 */
@OptIn(markerClass = ExperimentalCamera2Interop.class)
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class Camera2CameraInfoImpl implements CameraInfoInternal {

    private static final String TAG = "Camera2CameraInfo";
    private final String mCameraId;
    private final CameraCharacteristicsCompat mCameraCharacteristicsCompat;
    private final Camera2CameraInfo mCamera2CameraInfo;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    @Nullable
    private Camera2CameraControlImpl mCamera2CameraControlImpl;
    @GuardedBy("mLock")
    @Nullable
    private RedirectableLiveData<Integer> mRedirectTorchStateLiveData = null;
    @GuardedBy("mLock")
    @Nullable
    private RedirectableLiveData<ZoomState> mRedirectZoomStateLiveData = null;
    @NonNull
    private final RedirectableLiveData<CameraState> mCameraStateLiveData;
    @GuardedBy("mLock")
    @Nullable
    private List<Pair<CameraCaptureCallback, Executor>> mCameraCaptureCallbacks = null;

    @NonNull
    private final Quirks mCameraQuirks;
    @NonNull
    private final CamcorderProfileProvider mCamera2CamcorderProfileProvider;
    @NonNull
    private final CameraManagerCompat mCameraManager;

    /**
     * Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
     * called, camera control related API (torch/exposure/zoom) will return default values.
     */
    Camera2CameraInfoImpl(@NonNull String cameraId,
            @NonNull CameraManagerCompat cameraManager) throws CameraAccessExceptionCompat {
        mCameraId = Preconditions.checkNotNull(cameraId);
        mCameraManager = cameraManager;

        mCameraCharacteristicsCompat = cameraManager.getCameraCharacteristicsCompat(mCameraId);
        mCamera2CameraInfo = new Camera2CameraInfo(this);
        mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat);
        mCamera2CamcorderProfileProvider = new Camera2CamcorderProfileProvider(cameraId,
                mCameraCharacteristicsCompat);
        mCameraStateLiveData = new RedirectableLiveData<>(
                CameraState.create(CameraState.Type.CLOSED));
    }

    /**
     * Links with a {@link Camera2CameraControlImpl}. After the link, zoom/torch/exposure
     * operations of CameraControl will modify the states in this Camera2CameraInfoImpl.
     * Also, any CameraCaptureCallbacks added before this link will be added to the
     * {@link Camera2CameraControlImpl}.
     */
    void linkWithCameraControl(@NonNull Camera2CameraControlImpl camera2CameraControlImpl) {
        synchronized (mLock) {
            mCamera2CameraControlImpl = camera2CameraControlImpl;

            if (mRedirectZoomStateLiveData != null) {
                mRedirectZoomStateLiveData.redirectTo(
                        mCamera2CameraControlImpl.getZoomControl().getZoomState());
            }

            if (mRedirectTorchStateLiveData != null) {
                mRedirectTorchStateLiveData.redirectTo(
                        mCamera2CameraControlImpl.getTorchControl().getTorchState());
            }

            if (mCameraCaptureCallbacks != null) {
                for (Pair<CameraCaptureCallback, Executor> pair :
                        mCameraCaptureCallbacks) {
                    mCamera2CameraControlImpl.addSessionCameraCaptureCallback(pair.second,
                            pair.first);
                }
                mCameraCaptureCallbacks = null;
            }
        }
        logDeviceInfo();
    }

    /**
     * Sets the source of the {@linkplain CameraState camera states} that will be exposed. When
     * called more than once, the previous camera state source is overridden.
     */
    void setCameraStateSource(@NonNull LiveData<CameraState> cameraStateSource) {
        mCameraStateLiveData.redirectTo(cameraStateSource);
    }

    @NonNull
    @Override
    public String getCameraId() {
        return mCameraId;
    }

    @NonNull
    public CameraCharacteristicsCompat getCameraCharacteristicsCompat() {
        return mCameraCharacteristicsCompat;
    }

    @Nullable
    @Override
    public Integer getLensFacing() {
        Integer lensFacing = mCameraCharacteristicsCompat.get(CameraCharacteristics.LENS_FACING);
        Preconditions.checkNotNull(lensFacing);
        switch (lensFacing) {
            case CameraCharacteristics.LENS_FACING_FRONT:
                return CameraSelector.LENS_FACING_FRONT;
            case CameraCharacteristics.LENS_FACING_BACK:
                return CameraSelector.LENS_FACING_BACK;
            default:
                return null;
        }
    }

    @Override
    public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
        Integer sensorOrientation = getSensorOrientation();
        int relativeRotationDegrees =
                CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation);
        // Currently this assumes that a back-facing camera is always opposite to the screen.
        // This may not be the case for all devices, so in the future we may need to handle that
        // scenario.
        final Integer lensFacing = getLensFacing();
        boolean isOppositeFacingScreen =
                (lensFacing != null && CameraSelector.LENS_FACING_BACK == lensFacing);
        return CameraOrientationUtil.getRelativeImageRotation(
                relativeRotationDegrees,
                sensorOrientation,
                isOppositeFacingScreen);
    }

    int getSensorOrientation() {
        Integer sensorOrientation =
                mCameraCharacteristicsCompat.get(CameraCharacteristics.SENSOR_ORIENTATION);
        Preconditions.checkNotNull(sensorOrientation);
        return sensorOrientation;
    }

    int getSupportedHardwareLevel() {
        Integer deviceLevel =
                mCameraCharacteristicsCompat.get(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
        Preconditions.checkNotNull(deviceLevel);
        return deviceLevel;
    }

    @Override
    public int getSensorRotationDegrees() {
        return getSensorRotationDegrees(Surface.ROTATION_0);
    }

    private void logDeviceInfo() {
        // Extend by adding logging here as needed.
        logDeviceLevel();
    }

    private void logDeviceLevel() {
        String levelString;

        int deviceLevel = getSupportedHardwareLevel();
        switch (deviceLevel) {
            case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:
                levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY";
                break;
            case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL:
                levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL";
                break;
            case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:
                levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED";
                break;
            case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL:
                levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_FULL";
                break;
            case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3:
                levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_3";
                break;
            default:
                levelString = "Unknown value: " + deviceLevel;
                break;
        }
        Logger.i(TAG, "Device Level: " + levelString);
    }

    @Override
    public boolean hasFlashUnit() {
        return FlashAvailabilityChecker.isFlashAvailable(mCameraCharacteristicsCompat);
    }

    @NonNull
    @Override
    public LiveData<Integer> getTorchState() {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                if (mRedirectTorchStateLiveData == null) {
                    mRedirectTorchStateLiveData =
                            new RedirectableLiveData<>(TorchControl.DEFAULT_TORCH_STATE);
                }
                return mRedirectTorchStateLiveData;
            }

            // if RedirectableLiveData exists,  use it directly.
            if (mRedirectTorchStateLiveData != null) {
                return mRedirectTorchStateLiveData;
            }

            return mCamera2CameraControlImpl.getTorchControl().getTorchState();
        }
    }

    @NonNull
    @Override
    public LiveData<ZoomState> getZoomState() {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                if (mRedirectZoomStateLiveData == null) {
                    mRedirectZoomStateLiveData = new RedirectableLiveData<>(
                            ZoomControl.getDefaultZoomState(mCameraCharacteristicsCompat));
                }
                return mRedirectZoomStateLiveData;
            }

            // if RedirectableLiveData exists,  use it directly.
            if (mRedirectZoomStateLiveData != null) {
                return mRedirectZoomStateLiveData;
            }

            return mCamera2CameraControlImpl.getZoomControl().getZoomState();
        }
    }

    @NonNull
    @Override
    public ExposureState getExposureState() {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                return ExposureControl.getDefaultExposureState(mCameraCharacteristicsCompat);
            }
            return mCamera2CameraControlImpl.getExposureControl().getExposureState();
        }
    }

    @NonNull
    @Override
    public LiveData<CameraState> getCameraState() {
        return mCameraStateLiveData;
    }

    /**
     * {@inheritDoc}
     *
     * <p>When the CameraX configuration is {@link androidx.camera.camera2.Camera2Config}, the
     * return value depends on whether the device is legacy
     * ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL} {@code ==
     * }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY}).
     *
     * @return {@link #IMPLEMENTATION_TYPE_CAMERA2_LEGACY} if the device is legacy, otherwise
     * {@link #IMPLEMENTATION_TYPE_CAMERA2}.
     */
    @NonNull
    @Override
    public String getImplementationType() {
        final int hardwareLevel = getSupportedHardwareLevel();
        return hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
                ? IMPLEMENTATION_TYPE_CAMERA2_LEGACY : IMPLEMENTATION_TYPE_CAMERA2;
    }

    @Override
    public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                return false;
            }
            return mCamera2CameraControlImpl.getFocusMeteringControl().isFocusMeteringSupported(
                    action);
        }
    }

    @Override
    public boolean isYuvReprocessingSupported() {
        return isCapabilitySupported(mCameraCharacteristicsCompat,
                REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
    }

    @Override
    public boolean isPrivateReprocessingSupported() {
        return isCapabilitySupported(mCameraCharacteristicsCompat,
                REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
    }

    /** {@inheritDoc} */
    @NonNull
    @Override
    public CamcorderProfileProvider getCamcorderProfileProvider() {
        return mCamera2CamcorderProfileProvider;
    }

    @Override
    public void addSessionCaptureCallback(@NonNull Executor executor,
            @NonNull CameraCaptureCallback callback) {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                if (mCameraCaptureCallbacks == null) {
                    mCameraCaptureCallbacks = new ArrayList<>();
                }
                mCameraCaptureCallbacks.add(new Pair<>(callback, executor));
                return;
            }

            mCamera2CameraControlImpl.addSessionCameraCaptureCallback(executor, callback);
        }
    }

    @Override
    public void removeSessionCaptureCallback(@NonNull CameraCaptureCallback callback) {
        synchronized (mLock) {
            if (mCamera2CameraControlImpl == null) {
                if (mCameraCaptureCallbacks == null) {
                    return;
                }
                Iterator<Pair<CameraCaptureCallback, Executor>> it =
                        mCameraCaptureCallbacks.iterator();
                while (it.hasNext()) {
                    Pair<CameraCaptureCallback, Executor> pair = it.next();
                    if (pair.first == callback) {
                        it.remove();
                    }
                }
                return;
            }
            mCamera2CameraControlImpl.removeSessionCameraCaptureCallback(callback);
        }
    }

    /** {@inheritDoc} */
    @NonNull
    @Override
    public Quirks getCameraQuirks() {
        return mCameraQuirks;
    }

    /**
     * Gets the implementation of {@link Camera2CameraInfo}.
     */
    @NonNull
    public Camera2CameraInfo getCamera2CameraInfo() {
        return mCamera2CameraInfo;
    }

    /**
     * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
     *
     * <p>For every camera, the map contains at least the CameraCharacteristics for the camera id.
     * If the camera is logical camera, it will also contain associated physical camera ids and
     * their CameraCharacteristics.
     *
     */
    @NonNull
    public Map<String, CameraCharacteristics> getCameraCharacteristicsMap() {
        LinkedHashMap<String, CameraCharacteristics> map = new LinkedHashMap<>();

        map.put(mCameraId, mCameraCharacteristicsCompat.toCameraCharacteristics());

        for (String physicalCameraId : mCameraCharacteristicsCompat.getPhysicalCameraIds()) {
            if (Objects.equals(physicalCameraId, mCameraId)) {
                continue;
            }
            try {
                map.put(physicalCameraId,
                        mCameraManager.getCameraCharacteristicsCompat(physicalCameraId)
                                .toCameraCharacteristics());
            } catch (CameraAccessExceptionCompat e) {
                Logger.e(TAG,
                        "Failed to get CameraCharacteristics for cameraId " + physicalCameraId, e);
            }
        }
        return map;
    }

    /**
     * A {@link LiveData} which can be redirected to another {@link LiveData}. If no redirection
     * is set, initial value will be used.
     */
    static class RedirectableLiveData<T> extends MediatorLiveData<T> {
        private LiveData<T> mLiveDataSource;
        private T mInitialValue;

        RedirectableLiveData(T initialValue) {
            mInitialValue = initialValue;
        }

        void redirectTo(@NonNull LiveData<T> liveDataSource) {
            if (mLiveDataSource != null) {
                super.removeSource(mLiveDataSource);
            }
            mLiveDataSource = liveDataSource;
            super.addSource(liveDataSource, this::setValue);
        }

        @Override
        public <S> void addSource(@NonNull LiveData<S> source,
                @NonNull Observer<? super S> onChanged) {
            throw new UnsupportedOperationException();
        }

        // Overrides getValue() to reflect the correct value from source. This is required to ensure
        // getValue() is correct when observe() or observeForever() is not called.
        @Override
        public T getValue() {
            // Returns initial value if source is not set.
            return mLiveDataSource == null ? mInitialValue : mLiveDataSource.getValue();
        }
    }

}