blob: 178f4eeac9f9321ba9b6a35af7da0c474e1c4d5c [file] [log] [blame]
/*
* 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 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.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.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)
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() {
Boolean hasFlashUnit = mCameraCharacteristicsCompat.get(
CameraCharacteristics.FLASH_INFO_AVAILABLE);
Preconditions.checkNotNull(hasFlashUnit);
return hasFlashUnit;
}
@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);
}
}
/** {@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();
}
}
}