Merge changes from topic "Extensions1_3_supportedCameraOps" into androidx-main
* changes:
Fix the crash when invoking ImageCaptureExtenderImpl.getAvailableCaptureRequestKeys()
[Restrict CameraControl] Apply supportedCameraOperations to the CameraControl and CameraInfo.
[Restrict CameraControl] Add supportedCameraOperations to SessionProcessor
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
index d0fa9e3..fc3bf34 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
@@ -48,11 +48,12 @@
import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraInfoAdapter
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.internal.CameraUseCaseAdapter
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraXUtil
@@ -105,7 +106,7 @@
CameraSelector.LENS_FACING_BACK
).build()
camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
camera2CameraControl = cameraControl.camera2cameraControl
comboListener = camera2CameraControl.requestListener
}
@@ -438,7 +439,7 @@
Context.CAMERA_SERVICE
) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(
- (camera.cameraInfo as CameraInfoInternal).cameraId
+ camera.cameraInfo.toCameraInfoAdapter().cameraId
)
val maxDigitalZoom = characteristics.get(
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
index a072374..a4eb05c 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
@@ -61,6 +61,8 @@
import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraInfoAdapter
import androidx.camera.core.Camera
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector
@@ -70,7 +72,6 @@
import androidx.camera.core.Preview
import androidx.camera.core.SurfaceOrientedMeteringPointFactory
import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.DeferrableSurface
import androidx.camera.core.impl.Quirks
import androidx.camera.core.impl.SessionConfig
@@ -147,7 +148,7 @@
CameraSelector.LENS_FACING_BACK
).build()
camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
comboListener = cameraControl.camera2cameraControl.requestListener
characteristics = CameraUtil.getCameraCharacteristics(
@@ -473,7 +474,7 @@
}
private fun Camera.getMaxSupportedZoomRatio(): Float {
- return cameraInfo.zoomState.value!!.maxZoomRatio
+ return cameraInfo.toCameraInfoAdapter().zoomState.value!!.maxZoomRatio
}
private suspend fun verifyRequestOptions() {
@@ -506,7 +507,7 @@
cameraSelector,
*useCases,
)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
}
private fun createFakeRecordingUseCase(): FakeUseCase {
@@ -618,7 +619,7 @@
}
private fun Camera.getCameraQuirks(): Quirks {
- return (cameraInfo as? CameraInfoInternal)?.cameraQuirks!!
+ return cameraInfo.toCameraInfoAdapter().cameraQuirks
}
private fun CameraCharacteristics.isAfModeSupported(
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
index 3f62b210..c8d8825a 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
@@ -24,6 +24,7 @@
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.impl.CameraCaptureCallback
@@ -101,7 +102,7 @@
}
}
- cameraControl = camera!!.cameraControl as CameraControlAdapter
+ cameraControl = camera!!.cameraControl.toCameraControlAdapter()
}
@After
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
index 092a7c6..1c7730d 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
@@ -26,6 +26,7 @@
import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
@@ -92,7 +93,7 @@
CameraSelector.LENS_FACING_BACK
).build()
camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
@OptIn(ExperimentalCamera2Interop::class)
comboListener = cameraControl.camera2cameraControl.requestListener
@@ -267,6 +268,6 @@
}
},
)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
index 0366575..a1d918e 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
@@ -26,6 +26,7 @@
import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
import androidx.camera.core.CameraSelector
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.ImageCapture
@@ -115,7 +116,7 @@
imageCapture
)
- cameraControl = camera.cameraControl as CameraControlAdapter
+ cameraControl = camera.cameraControl.toCameraControlAdapter()
@OptIn(ExperimentalCamera2Interop::class)
comboListener = cameraControl.camera2cameraControl.requestListener
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt
new file mode 100644
index 0000000..9a7f3fc
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.camera2.pipe.testing
+
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
+import androidx.camera.core.CameraControl
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.impl.CameraControlInternal
+import androidx.camera.core.impl.CameraInfoInternal
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+fun CameraControl.toCameraControlAdapter(): CameraControlAdapter {
+ return ((this as CameraControlInternal).implementation) as CameraControlAdapter
+}
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+fun CameraInfo.toCameraInfoAdapter(): CameraInfoAdapter {
+ return ((this as CameraInfoInternal).implementation) as CameraInfoAdapter
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
index d692dbe..2ca7aeb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
@@ -27,6 +27,7 @@
import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraControl
import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
import androidx.camera.core.CameraControl
+import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.utils.futures.Futures
import androidx.core.util.Preconditions
import com.google.common.util.concurrent.ListenableFuture
@@ -186,11 +187,12 @@
*/
@JvmStatic
fun from(cameraControl: CameraControl): Camera2CameraControl {
+ var cameraControlImpl = (cameraControl as CameraControlInternal).implementation
Preconditions.checkArgument(
- cameraControl is CameraControlAdapter,
+ cameraControlImpl is CameraControlAdapter,
"CameraControl doesn't contain Camera2 implementation."
)
- return (cameraControl as CameraControlAdapter).camera2cameraControl
+ return (cameraControlImpl as CameraControlAdapter).camera2cameraControl
}
/**
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
index 2372dd3..3094fc4 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
@@ -23,6 +23,7 @@
import androidx.camera.camera2.pipe.integration.compat.workaround.getSafely
import androidx.camera.camera2.pipe.integration.impl.CameraProperties
import androidx.camera.core.CameraInfo
+import androidx.camera.core.impl.CameraInfoInternal
import androidx.core.util.Preconditions
/**
@@ -88,11 +89,12 @@
*/
@JvmStatic
fun from(@Suppress("UNUSED_PARAMETER") cameraInfo: CameraInfo): Camera2CameraInfo {
+ var cameraInfoImpl = (cameraInfo as CameraInfoInternal).implementation
Preconditions.checkArgument(
- cameraInfo is CameraInfoAdapter,
+ cameraInfoImpl is CameraInfoAdapter,
"CameraInfo doesn't contain Camera2 implementation."
)
- return (cameraInfo as CameraInfoAdapter).camera2CameraInfo
+ return (cameraInfoImpl as CameraInfoAdapter).camera2CameraInfo
}
/**
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
index 2f3e542..0a53825 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
@@ -59,6 +59,7 @@
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler;
+import androidx.camera.camera2.internal.util.TestUtil;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraXConfig;
@@ -431,7 +432,7 @@
imageCapture);
Camera2CameraControlImpl camera2CameraControlImpl =
- (Camera2CameraControlImpl) mCamera.getCameraControl();
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
CameraCaptureCallback captureCallback = mock(CameraCaptureCallback.class);
CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
@@ -458,7 +459,7 @@
ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
imageAnalysis);
- return (Camera2CameraControlImpl) mCamera.getCameraControl();
+ return TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
}
private <T> void assertArraySize(T[] array, int expectedSize) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
index 8143d94..3e6a382 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
@@ -21,6 +21,7 @@
import android.content.Context;
import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.internal.util.TestUtil;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraXConfig;
import androidx.camera.core.ImageAnalysis;
@@ -82,8 +83,8 @@
// Make ImageAnalysis active.
imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
mCamera = CameraUtil.createCameraAndAttachUseCase(context, cameraSelector, imageAnalysis);
- Camera2CameraControlImpl cameraControl = (Camera2CameraControlImpl)
- mCamera.getCameraControl();
+ Camera2CameraControlImpl cameraControl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
mTorchControl = cameraControl.getTorchControl();
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java
new file mode 100644
index 0000000..cd4f5ef
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.camera2.internal.util;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.Camera2CameraControlImpl;
+import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.CameraControlInternal;
+import androidx.camera.core.impl.CameraInfoInternal;
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class TestUtil {
+ public static Camera2CameraControlImpl getCamera2CameraControlImpl(
+ CameraControl cameraControl) {
+ if (cameraControl instanceof CameraControlInternal) {
+ CameraControlInternal impl =
+ ((CameraControlInternal) cameraControl).getImplementation();
+ return (Camera2CameraControlImpl) impl;
+ }
+ throw new IllegalArgumentException(
+ "Can't get Camera2CameraControlImpl from the CameraControl");
+ }
+
+ public static Camera2CameraInfoImpl getCamera2CameraInfoImpl(CameraInfo cameraInfo) {
+ if (cameraInfo instanceof CameraInfoInternal) {
+ CameraInfoInternal impl = ((CameraInfoInternal) cameraInfo).getImplementation();
+ return (Camera2CameraInfoImpl) impl;
+ }
+ throw new IllegalArgumentException(
+ "Can't get Camera2CameraInfoImpl from the CameraInfo");
+ }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
index 7977243..2566caf 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
@@ -39,9 +39,9 @@
import androidx.annotation.OptIn;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.camera2.internal.Camera2CameraControlImpl;
+import androidx.camera.camera2.internal.util.TestUtil;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.internal.CameraUseCaseAdapter;
import androidx.camera.testing.CameraUtil;
@@ -94,7 +94,8 @@
mCameraSelector = new CameraSelector.Builder().requireLensFacing(
CameraSelector.LENS_FACING_BACK).build();
mCamera = CameraUtil.createCameraUseCaseAdapter(mContext, mCameraSelector);
- mCamera2CameraControlImpl = (Camera2CameraControlImpl) mCamera.getCameraControl();
+ mCamera2CameraControlImpl =
+ TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
mCamera2CameraControl = mCamera2CameraControlImpl.getCamera2CameraControl();
mMockCaptureCallback = mock(CameraCaptureSession.CaptureCallback.class);
}
@@ -267,7 +268,7 @@
private Rect getZoom2XCropRegion() throws Exception {
AtomicReference<String> cameraIdRef = new AtomicReference<>();
- String cameraId = ((CameraInfoInternal) mCamera.getCameraInfo()).getCameraId();
+ String cameraId = TestUtil.getCamera2CameraInfoImpl(mCamera.getCameraInfo()).getCameraId();
cameraIdRef.set(cameraId);
CameraManager cameraManager =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
index 6e9ed1d..860400e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
@@ -20,12 +20,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
+import androidx.camera.core.Logger;
+import java.util.Collections;
import java.util.Set;
@RequiresApi(28)
class CameraCharacteristicsApi28Impl extends CameraCharacteristicsBaseImpl{
-
+ private static final String TAG = "CameraCharacteristicsImpl";
CameraCharacteristicsApi28Impl(@NonNull CameraCharacteristics cameraCharacteristics) {
super(cameraCharacteristics);
}
@@ -33,6 +35,14 @@
@NonNull
@Override
public Set<String> getPhysicalCameraIds() {
- return mCameraCharacteristics.getPhysicalCameraIds();
+ try {
+ return mCameraCharacteristics.getPhysicalCameraIds();
+ } catch (Exception e) {
+ // getPhysicalCameraIds could cause crash in Robolectric and there is no known
+ // workaround
+ Logger.e(TAG,
+ "CameraCharacteristics.getPhysicalCameraIds throws an exception.", e);
+ return Collections.emptySet();
+ }
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
index 3591ef3..0b8f4b9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
@@ -25,6 +25,7 @@
import androidx.camera.camera2.internal.Camera2CameraControlImpl;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
import androidx.camera.core.CameraControl;
+import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.TagBundle;
import androidx.camera.core.impl.annotation.ExecutedBy;
@@ -125,9 +126,11 @@
*/
@NonNull
public static Camera2CameraControl from(@NonNull CameraControl cameraControl) {
- Preconditions.checkArgument(cameraControl instanceof Camera2CameraControlImpl,
+ CameraControlInternal cameraControlImpl =
+ ((CameraControlInternal) cameraControl).getImplementation();
+ Preconditions.checkArgument(cameraControlImpl instanceof Camera2CameraControlImpl,
"CameraControl doesn't contain Camera2 implementation.");
- return ((Camera2CameraControlImpl) cameraControl).getCamera2CameraControl();
+ return ((Camera2CameraControlImpl) cameraControlImpl).getCamera2CameraControl();
}
/**
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
index 5e18f87..33522d2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
@@ -25,6 +25,7 @@
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.CameraInfoInternal;
import androidx.core.util.Preconditions;
import java.util.Map;
@@ -58,9 +59,11 @@
*/
@NonNull
public static Camera2CameraInfo from(@NonNull CameraInfo cameraInfo) {
- Preconditions.checkArgument(cameraInfo instanceof Camera2CameraInfoImpl,
+ CameraInfoInternal cameraInfoImpl =
+ ((CameraInfoInternal) cameraInfo).getImplementation();
+ Preconditions.checkArgument(cameraInfoImpl instanceof Camera2CameraInfoImpl,
"CameraInfo doesn't contain Camera2 implementation.");
- return ((Camera2CameraInfoImpl) cameraInfo).getCamera2CameraInfo();
+ return ((Camera2CameraInfoImpl) cameraInfoImpl).getCamera2CameraInfo();
}
/**
@@ -119,9 +122,10 @@
@NonNull
public static CameraCharacteristics extractCameraCharacteristics(
@NonNull CameraInfo cameraInfo) {
- Preconditions.checkState(cameraInfo instanceof Camera2CameraInfoImpl, "CameraInfo does "
- + "not contain any Camera2 information.");
- Camera2CameraInfoImpl impl = (Camera2CameraInfoImpl) cameraInfo;
+ CameraInfoInternal cameraInfoImpl = ((CameraInfoInternal) cameraInfo).getImplementation();
+ Preconditions.checkState(cameraInfoImpl instanceof Camera2CameraInfoImpl,
+ "CameraInfo does not contain any Camera2 information.");
+ Camera2CameraInfoImpl impl = (Camera2CameraInfoImpl) cameraInfoImpl;
return impl.getCameraCharacteristicsCompat().toCameraCharacteristics();
}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
index 2276320..8ad3050 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
@@ -83,6 +83,7 @@
Camera2CameraInfo camera2CameraInfo = mock(Camera2CameraInfo.class);
Camera2CameraInfoImpl cameraInfoImpl = mock(Camera2CameraInfoImpl.class);
when(cameraInfoImpl.getCamera2CameraInfo()).thenAnswer(ignored -> camera2CameraInfo);
+ when(cameraInfoImpl.getImplementation()).thenAnswer(ignored -> cameraInfoImpl);
Camera2CameraInfo resultCamera2CameraInfo = Camera2CameraInfo.from(cameraInfoImpl);
assertThat(resultCamera2CameraInfo).isEqualTo(camera2CameraInfo);
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 56a6ebb..c789d6d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -47,6 +47,8 @@
testImplementation(libs.truth)
testImplementation(libs.robolectric)
testImplementation(libs.mockitoCore4)
+ testImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
+ testImplementation(project(":internal-testutils-truth"))
testImplementation(project(":camera:camera-testing"), {
exclude group: "androidx.camera", module: "camera-core"
})
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index a385c37..32462ac 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -42,6 +42,7 @@
import androidx.camera.core.concurrent.CameraCoordinator;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureMetaData;
+import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.ImageCaptureConfig;
import androidx.camera.core.impl.StreamSpec;
@@ -142,7 +143,7 @@
ImageCapture.OnImageCapturedCallback callback = mock(
ImageCapture.OnImageCapturedCallback.class);
FakeCameraControl fakeCameraControl =
- ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+ getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
// Notify the cancel after the capture request has been successfully submitted
@@ -173,7 +174,7 @@
ImageCapture.OnImageCapturedCallback callback = mock(
ImageCapture.OnImageCapturedCallback.class);
FakeCameraControl fakeCameraControl =
- ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+ getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
// Notify the failure after the capture request has been successfully submitted
fakeCameraControl.notifyAllRequestsOnCaptureFailed();
@@ -309,6 +310,11 @@
assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
}
+ private FakeCameraControl getCameraControlImplementation(CameraControl cameraControl) {
+ CameraControlInternal impl = ((CameraControlInternal) cameraControl).getImplementation();
+ return (FakeCameraControl) impl;
+ }
+
@NonNull
private List<CaptureConfig> captureImage(@NonNull ImageCapture imageCapture,
@NonNull Class<?> callbackClass) {
@@ -342,7 +348,7 @@
}
FakeCameraControl fakeCameraControl =
- ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+ getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
FakeCameraControl.OnNewCaptureRequestListener mockCaptureRequestListener =
mock(FakeCameraControl.OnNewCaptureRequestListener.class);
fakeCameraControl.setOnNewCaptureRequestListener(mockCaptureRequestListener);
@@ -510,7 +516,7 @@
ImageCapture.OnImageCapturedCallback callback = mock(
ImageCapture.OnImageCapturedCallback.class);
FakeCameraControl fakeCameraControl =
- ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+ getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
CountDownLatch latch = new CountDownLatch(1);
fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
latch.countDown();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
index 30f3edb..2faa545 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
@@ -186,6 +186,17 @@
}
/**
+ * Create a Builder from a {@link FocusMeteringAction}.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public Builder(@NonNull FocusMeteringAction focusMeteringAction) {
+ mMeteringPointsAf.addAll(focusMeteringAction.getMeteringPointsAf());
+ mMeteringPointsAe.addAll(focusMeteringAction.getMeteringPointsAe());
+ mMeteringPointsAwb.addAll(focusMeteringAction.getMeteringPointsAwb());
+ mAutoCancelDurationInMillis = focusMeteringAction.getAutoCancelDurationInMillis();
+ }
+
+ /**
* Adds another {@link MeteringPoint} with default metering mode {@link #FLAG_AF} |
* {@link #FLAG_AE} | {@link #FLAG_AWB}.
*
@@ -271,6 +282,27 @@
}
/**
+ *
+ * Remove all points of the given meteringMode.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ public Builder removePoints(@MeteringMode int meteringMode) {
+ if ((meteringMode & FLAG_AF) != 0) {
+ mMeteringPointsAf.clear();
+ }
+
+ if ((meteringMode & FLAG_AE) != 0) {
+ mMeteringPointsAe.clear();
+ }
+
+ if ((meteringMode & FLAG_AWB) != 0) {
+ mMeteringPointsAwb.clear();
+ }
+ return this;
+ }
+
+ /**
* Builds the {@link FocusMeteringAction} instance.
*/
@NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
index 33688e9..c89bb2f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
@@ -39,9 +39,13 @@
/**
* The CameraControlInternal Interface.
*
+ *
* <p>CameraControlInternal is used for global camera operations like zoom, focus, flash and
- * triggering
- * AF/AE.
+ * triggering AF/AE as well as some internal operations.
+ *
+ * <p>{@link #getImplementation()} returns a {@link CameraControlInternal} instance
+ * that contains the actual implementation and can be cast to an implementation specific class.
+ * If the instance itself is the implementation instance, then it should return <code>this</code>.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public interface CameraControlInternal extends CameraControl {
@@ -136,6 +140,16 @@
@NonNull
Config getInteropConfig();
+ /**
+ * Gets the underlying implementation instance which could be cast into an implementation
+ * specific class for further use in implementation module. Returns <code>this</code> if this
+ * instance is the implementation instance.
+ */
+ @NonNull
+ default CameraControlInternal getImplementation() {
+ return this;
+ }
+
CameraControlInternal DEFAULT_EMPTY_INSTANCE = new CameraControlInternal() {
@FlashMode
@Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
index 791efe0..e6a1e9b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
@@ -36,6 +36,10 @@
* An interface for retrieving camera information.
*
* <p>Contains methods for retrieving characteristics for a specific camera.
+ *
+ * <p>{@link #getImplementation()} returns a {@link CameraInfoInternal} instance
+ * that contains the actual implementation and can be cast to an implementation specific class.
+ * If the instance itself is the implementation instance, then it should return <code>this</code>.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public interface CameraInfoInternal extends CameraInfo {
@@ -101,6 +105,17 @@
@NonNull
Set<DynamicRange> getSupportedDynamicRanges();
+ /**
+ * Gets the underlying implementation instance which could be cast into an implementation
+ * specific class for further use in implementation module. Returns <code>this</code> if this
+ * instance is the implementation instance.
+ */
+ @NonNull
+ default CameraInfoInternal getImplementation() {
+ return this;
+ }
+
+
/** {@inheritDoc} */
@NonNull
@Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
new file mode 100644
index 0000000..ea73135
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
@@ -0,0 +1,155 @@
+/*
+ * 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.core.impl;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
+import androidx.camera.core.ImageCapture;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * A {@link CameraControlInternal} that forwards all the calls into the given
+ * {@link CameraControlInternal}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForwardingCameraControl implements CameraControlInternal {
+ private final CameraControlInternal mCameraControlInternal;
+
+ /**
+ * Create an instance that will forward all calls to the supplied {@link CameraControlInternal}
+ * instance.
+ */
+ public ForwardingCameraControl(@NonNull CameraControlInternal cameraControlInternal) {
+ mCameraControlInternal = cameraControlInternal;
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> enableTorch(boolean torch) {
+ return mCameraControlInternal.enableTorch(torch);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
+ @NonNull FocusMeteringAction action) {
+ return mCameraControlInternal.startFocusAndMetering(action);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> cancelFocusAndMetering() {
+ return mCameraControlInternal.cancelFocusAndMetering();
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> setZoomRatio(float ratio) {
+ return mCameraControlInternal.setZoomRatio(ratio);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> setLinearZoom(float linearZoom) {
+ return mCameraControlInternal.setLinearZoom(linearZoom);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
+ return mCameraControlInternal.setExposureCompensationIndex(value);
+ }
+
+ @Override
+ @ImageCapture.FlashMode
+ public int getFlashMode() {
+ return mCameraControlInternal.getFlashMode();
+ }
+
+ @Override
+ public void setFlashMode(@ImageCapture.FlashMode int flashMode) {
+ mCameraControlInternal.setFlashMode(flashMode);
+ }
+
+ @Override
+ public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
+ mCameraControlInternal.addZslConfig(sessionConfigBuilder);
+ }
+
+ @Override
+ public void setZslDisabledByUserCaseConfig(boolean disabled) {
+ mCameraControlInternal.setZslDisabledByUserCaseConfig(disabled);
+ }
+
+ @Override
+ public boolean isZslDisabledByByUserCaseConfig() {
+ return mCameraControlInternal.isZslDisabledByByUserCaseConfig();
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<List<Void>> submitStillCaptureRequests(
+ @NonNull List<CaptureConfig> captureConfigs,
+ @ImageCapture.CaptureMode int captureMode,
+ @ImageCapture.FlashType int flashType) {
+ return mCameraControlInternal.submitStillCaptureRequests(
+ captureConfigs,
+ captureMode,
+ flashType);
+ }
+
+ @NonNull
+ @Override
+ public SessionConfig getSessionConfig() {
+ return mCameraControlInternal.getSessionConfig();
+ }
+
+ @NonNull
+ @Override
+ public Rect getSensorRect() {
+ return mCameraControlInternal.getSensorRect();
+ }
+
+ @Override
+ public void addInteropConfig(@NonNull Config config) {
+ mCameraControlInternal.addInteropConfig(config);
+ }
+
+ @Override
+ public void clearInteropConfig() {
+ mCameraControlInternal.clearInteropConfig();
+ }
+
+ @NonNull
+ @Override
+ public Config getInteropConfig() {
+ return mCameraControlInternal.getInteropConfig();
+ }
+
+ @NonNull
+ @Override
+ public CameraControlInternal getImplementation() {
+ return mCameraControlInternal.getImplementation();
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
new file mode 100644
index 0000000..c389a1d
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
@@ -0,0 +1,195 @@
+/*
+ * 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.core.impl;
+
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CameraState;
+import androidx.camera.core.DynamicRange;
+import androidx.camera.core.ExperimentalZeroShutterLag;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.ZoomState;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link CameraInfoInternal} that forwards all the calls into the given
+ * {@link CameraInfoInternal}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForwardingCameraInfo implements CameraInfoInternal {
+
+ private final CameraInfoInternal mCameraInfoInternal;
+
+ /**
+ * Create an instance that will forward all calls to the supplied {@link CameraInfoInternal}
+ * instance.
+ */
+ public ForwardingCameraInfo(@NonNull CameraInfoInternal cameraInfoInternal) {
+ mCameraInfoInternal = cameraInfoInternal;
+ }
+
+ @Override
+ public int getSensorRotationDegrees() {
+ return mCameraInfoInternal.getSensorRotationDegrees();
+ }
+
+ @Override
+ public int getSensorRotationDegrees(int relativeRotation) {
+ return mCameraInfoInternal.getSensorRotationDegrees(relativeRotation);
+ }
+
+ @Override
+ public boolean hasFlashUnit() {
+ return mCameraInfoInternal.hasFlashUnit();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<Integer> getTorchState() {
+ return mCameraInfoInternal.getTorchState();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<ZoomState> getZoomState() {
+ return mCameraInfoInternal.getZoomState();
+ }
+
+ @NonNull
+ @Override
+ public ExposureState getExposureState() {
+ return mCameraInfoInternal.getExposureState();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<CameraState> getCameraState() {
+ return mCameraInfoInternal.getCameraState();
+ }
+
+ @NonNull
+ @Override
+ public String getImplementationType() {
+ return mCameraInfoInternal.getImplementationType();
+ }
+
+ @Override
+ public int getLensFacing() {
+ return mCameraInfoInternal.getLensFacing();
+ }
+
+ @Override
+ public float getIntrinsicZoomRatio() {
+ return mCameraInfoInternal.getIntrinsicZoomRatio();
+ }
+
+ @Override
+ public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
+ return mCameraInfoInternal.isFocusMeteringSupported(action);
+ }
+
+ @Override
+ @ExperimentalZeroShutterLag
+ public boolean isZslSupported() {
+ return mCameraInfoInternal.isZslSupported();
+ }
+
+ @NonNull
+ @Override
+ public Set<Range<Integer>> getSupportedFrameRateRanges() {
+ return mCameraInfoInternal.getSupportedFrameRateRanges();
+ }
+
+ @Override
+ public boolean isPrivateReprocessingSupported() {
+ return mCameraInfoInternal.isPrivateReprocessingSupported();
+ }
+
+ @NonNull
+ @Override
+ public String getCameraId() {
+ return mCameraInfoInternal.getCameraId();
+ }
+
+ @Override
+ public void addSessionCaptureCallback(@NonNull Executor executor,
+ @NonNull CameraCaptureCallback callback) {
+ mCameraInfoInternal.addSessionCaptureCallback(executor, callback);
+ }
+
+ @Override
+ public void removeSessionCaptureCallback(@NonNull CameraCaptureCallback callback) {
+ mCameraInfoInternal.removeSessionCaptureCallback(callback);
+ }
+
+ @NonNull
+ @Override
+ public Quirks getCameraQuirks() {
+ return mCameraInfoInternal.getCameraQuirks();
+ }
+
+ @NonNull
+ @Override
+ public EncoderProfilesProvider getEncoderProfilesProvider() {
+ return mCameraInfoInternal.getEncoderProfilesProvider();
+ }
+
+ @NonNull
+ @Override
+ public Timebase getTimebase() {
+ return mCameraInfoInternal.getTimebase();
+ }
+
+ @NonNull
+ @Override
+ public List<Size> getSupportedResolutions(int format) {
+ return mCameraInfoInternal.getSupportedResolutions(format);
+ }
+
+ @NonNull
+ @Override
+ public List<Size> getSupportedHighResolutions(int format) {
+ return mCameraInfoInternal.getSupportedHighResolutions(format);
+ }
+
+ @NonNull
+ @Override
+ public Set<DynamicRange> getSupportedDynamicRanges() {
+ return mCameraInfoInternal.getSupportedDynamicRanges();
+ }
+
+ @NonNull
+ @Override
+ public CameraInfoInternal getImplementation() {
+ return mCameraInfoInternal.getImplementation();
+ }
+
+ @NonNull
+ @Override
+ public CameraSelector getCameraSelector() {
+ return mCameraInfoInternal.getCameraSelector();
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
new file mode 100644
index 0000000..6fc61f8
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
@@ -0,0 +1,199 @@
+/*
+ * 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.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
+import androidx.camera.core.impl.utils.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link CameraControlInternal} whose capabilities can be restricted via
+ * {@link #enableRestrictedOperations(boolean, Set)}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class RestrictedCameraControl extends ForwardingCameraControl {
+ /**
+ * Defines the list of supported camera operations.
+ */
+ public static final int ZOOM = 0;
+ public static final int AUTO_FOCUS = 1;
+ public static final int AF_REGION = 2;
+ public static final int AE_REGION = 3;
+ public static final int AWB_REGION = 4;
+ public static final int FLASH = 5;
+ public static final int TORCH = 6;
+ public static final int EXPOSURE_COMPENSATION = 7;
+
+ public @interface CameraOperation {
+ }
+
+ private final CameraControlInternal mCameraControl;
+ private volatile boolean mUseRestrictedCameraOperations = false;
+ @Nullable
+ private volatile @CameraOperation Set<Integer> mRestrictedCameraOperations;
+
+ /**
+ * Creates the restricted version of the given {@link CameraControlInternal}.
+ */
+ public RestrictedCameraControl(@NonNull CameraControlInternal cameraControl) {
+ super(cameraControl);
+ mCameraControl = cameraControl;
+ }
+
+ /**
+ * Enable or disable the restricted operations. If disabled, it works just like the origin
+ * CameraControlInternal instance.
+ */
+ public void enableRestrictedOperations(boolean enable,
+ @Nullable @CameraOperation Set<Integer> restrictedOperations) {
+ mUseRestrictedCameraOperations = enable;
+ mRestrictedCameraOperations = restrictedOperations;
+ }
+
+ /**
+ * Returns implementation instance.
+ */
+ @NonNull
+ @Override
+ public CameraControlInternal getImplementation() {
+ return mCameraControl;
+ }
+
+ boolean isOperationSupported(
+ @NonNull @CameraOperation int... operations) {
+ if (!mUseRestrictedCameraOperations || mRestrictedCameraOperations == null) {
+ return true;
+ }
+
+ // Arrays.asList doesn't work for int array.
+ List<Integer> operationList = new ArrayList<>(operations.length);
+ for (int operation : operations) {
+ operationList.add(operation);
+ }
+
+ return mRestrictedCameraOperations.containsAll(operationList);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> enableTorch(boolean torch) {
+ if (!isOperationSupported(TORCH)) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Torch is not supported"));
+ }
+ return mCameraControl.enableTorch(torch);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
+ @NonNull FocusMeteringAction action) {
+ FocusMeteringAction modifiedAction = getModifiedFocusMeteringAction(action);
+ if (modifiedAction == null) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("FocusMetering is not supported"));
+ }
+
+ return mCameraControl.startFocusAndMetering(modifiedAction);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> cancelFocusAndMetering() {
+ return mCameraControl.cancelFocusAndMetering();
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> setZoomRatio(float ratio) {
+ if (!isOperationSupported(ZOOM)) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Zoom is not supported"));
+ }
+ return mCameraControl.setZoomRatio(ratio);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Void> setLinearZoom(float linearZoom) {
+ if (!isOperationSupported(ZOOM)) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Zoom is not supported"));
+ }
+ return mCameraControl.setLinearZoom(linearZoom);
+ }
+
+ @NonNull
+ @Override
+ public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
+ if (!isOperationSupported(EXPOSURE_COMPENSATION)) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("ExposureCompensation is not supported"));
+ }
+ return mCameraControl.setExposureCompensationIndex(value);
+ }
+
+ /**
+ * Returns the modified {@link FocusMeteringAction} that filters out unsupported AE/AF/AWB
+ * regions. Returns null if none of AF/AE/AWB regions can be supported after the filtering.
+ */
+ @Nullable
+ FocusMeteringAction getModifiedFocusMeteringAction(@NonNull FocusMeteringAction action) {
+ boolean shouldModify = false;
+ FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(action);
+ if (!action.getMeteringPointsAf().isEmpty()
+ && !isOperationSupported(AUTO_FOCUS, AF_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AF);
+ }
+
+ if (!action.getMeteringPointsAe().isEmpty()
+ && !isOperationSupported(AE_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AE);
+ }
+
+ if (!action.getMeteringPointsAwb().isEmpty()
+ && !isOperationSupported(AWB_REGION)) {
+ shouldModify = true;
+ builder.removePoints(FocusMeteringAction.FLAG_AWB);
+ }
+
+ // Returns origin action if no need to modify.
+ if (!shouldModify) {
+ return action;
+ }
+
+ FocusMeteringAction modifyAction = builder.build();
+ if (modifyAction.getMeteringPointsAf().isEmpty()
+ && modifyAction.getMeteringPointsAe().isEmpty()
+ && modifyAction.getMeteringPointsAwb().isEmpty()) {
+ // All regions are not allowed, return null.
+ return null;
+ }
+ return builder.build();
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
new file mode 100644
index 0000000..b2148f5
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
@@ -0,0 +1,123 @@
+/*
+ * 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.core.impl;
+
+import android.util.Range;
+import android.util.Rational;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.TorchState;
+import androidx.camera.core.ZoomState;
+import androidx.camera.core.internal.ImmutableZoomState;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * A {@link CameraInfoInternal} that returns disabled state if the corresponding operation in the
+ * given {@link RestrictedCameraControl} is disabled.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class RestrictedCameraInfo extends ForwardingCameraInfo {
+ private final CameraInfoInternal mCameraInfo;
+ private final RestrictedCameraControl mRestrictedCameraControl;
+
+ public RestrictedCameraInfo(@NonNull CameraInfoInternal cameraInfo,
+ @NonNull RestrictedCameraControl restrictedCameraControl) {
+ super(cameraInfo);
+ mCameraInfo = cameraInfo;
+ mRestrictedCameraControl = restrictedCameraControl;
+ }
+
+ @NonNull
+ @Override
+ public CameraInfoInternal getImplementation() {
+ return mCameraInfo;
+ }
+
+ @Override
+ public boolean hasFlashUnit() {
+ if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.FLASH)) {
+ return false;
+ }
+
+ return mCameraInfo.hasFlashUnit();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<Integer> getTorchState() {
+ if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.TORCH)) {
+ return new MutableLiveData<>(TorchState.OFF);
+ }
+
+ return mCameraInfo.getTorchState();
+ }
+
+ @NonNull
+ @Override
+ public LiveData<ZoomState> getZoomState() {
+ if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.ZOOM)) {
+ return new MutableLiveData<>(ImmutableZoomState.create(
+ /* zoomRatio */1f, /* maxZoomRatio */ 1f,
+ /* minZoomRatio */ 1f, /* linearZoom*/ 0f));
+ }
+ return mCameraInfo.getZoomState();
+ }
+
+ @NonNull
+ @Override
+ public ExposureState getExposureState() {
+ if (!mRestrictedCameraControl.isOperationSupported(
+ RestrictedCameraControl.EXPOSURE_COMPENSATION)) {
+ return new ExposureState() {
+ @Override
+ public int getExposureCompensationIndex() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public Range<Integer> getExposureCompensationRange() {
+ return new Range<>(0, 0);
+ }
+
+ @NonNull
+ @Override
+ public Rational getExposureCompensationStep() {
+ return Rational.ZERO;
+ }
+
+ @Override
+ public boolean isExposureCompensationSupported() {
+ return false;
+ }
+ };
+ }
+ return mCameraInfo.getExposureState();
+ }
+
+ @Override
+ public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
+ if (mRestrictedCameraControl.getModifiedFocusMeteringAction(action) == null) {
+ return false;
+ }
+ return mCameraInfo.isFocusMeteringSupported(action);
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
index f9858f5..70b5c2b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
@@ -24,7 +24,9 @@
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraInfo;
+import java.util.Collections;
import java.util.Map;
+import java.util.Set;
/**
* A processor for (1) transforming the surfaces used in Preview/ImageCapture/ImageAnalysis
@@ -127,6 +129,14 @@
}
/**
+ * Returns the supported camera operations when the SessionProcessor is enabled.
+ */
+ @NonNull
+ default @RestrictedCameraControl.CameraOperation Set<Integer> getSupportedCameraOperations() {
+ return Collections.emptySet();
+ }
+
+ /**
* Callback for {@link #startRepeating} and {@link #startCapture}.
*/
interface CaptureCallback {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 9d3021c..4e74b5e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -59,6 +59,10 @@
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CameraMode;
import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.RestrictedCameraControl;
+import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
+import androidx.camera.core.impl.RestrictedCameraInfo;
+import androidx.camera.core.impl.SessionProcessor;
import androidx.camera.core.impl.StreamSpec;
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.UseCaseConfig;
@@ -143,6 +147,12 @@
@Nullable
private StreamSharing mStreamSharing;
+ @NonNull
+ private final RestrictedCameraControl mRestrictedCameraControl;
+ @NonNull
+ private final RestrictedCameraInfo mRestrictedCameraInfo;
+
+
/**
* Create a new {@link CameraUseCaseAdapter} instance.
*
@@ -167,6 +177,12 @@
mCameraCoordinator = cameraCoordinator;
mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
mUseCaseConfigFactory = useCaseConfigFactory;
+ // TODO(b/279996499): bind the same restricted CameraControl and CameraInfo to use cases.
+ mRestrictedCameraControl =
+ new RestrictedCameraControl(mCameraInternal.getCameraControlInternal());
+ mRestrictedCameraInfo =
+ new RestrictedCameraInfo(mCameraInternal.getCameraInfoInternal(),
+ mRestrictedCameraControl);
}
/**
@@ -603,14 +619,14 @@
Map<UseCaseConfig<?>, List<Size>> configToSupportedSizesMap = new HashMap<>();
Rect sensorRect;
try {
- sensorRect = ((CameraControlInternal) getCameraControl()).getSensorRect();
+ sensorRect = mCameraInternal.getCameraControlInternal().getSensorRect();
} catch (NullPointerException e) {
// TODO(b/274531208): Remove the unnecessary SENSOR_INFO_ACTIVE_ARRAY_SIZE NPE
// check related code only which is used for robolectric tests
sensorRect = null;
}
SupportedOutputSizesSorter supportedOutputSizesSorter = new SupportedOutputSizesSorter(
- (CameraInfoInternal) getCameraInfo(),
+ cameraInfoInternal,
sensorRect != null ? rectToSize(sensorRect) : null);
for (UseCase useCase : newUseCases) {
ConfigPair configPair = configPairMap.get(useCase);
@@ -809,13 +825,13 @@
@NonNull
@Override
public CameraControl getCameraControl() {
- return mCameraInternal.getCameraControlInternal();
+ return mRestrictedCameraControl;
}
@NonNull
@Override
public CameraInfo getCameraInfo() {
- return mCameraInternal.getCameraInfoInternal();
+ return mRestrictedCameraInfo;
}
@NonNull
@@ -846,6 +862,14 @@
}
mCameraConfig = cameraConfig;
+ SessionProcessor sessionProcessor = mCameraConfig.getSessionProcessor(null);
+ if (sessionProcessor != null) {
+ @CameraOperation Set<Integer> supportedOps =
+ sessionProcessor.getSupportedCameraOperations();
+ mRestrictedCameraControl.enableRestrictedOperations(true, supportedOps);
+ } else {
+ mRestrictedCameraControl.enableRestrictedOperations(false, null);
+ }
//Configure the CameraInternal as well so that it can get SessionProcessor.
mCameraInternal.setExtendedConfig(mCameraConfig);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
index 1ca8ace..294173d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
@@ -20,18 +20,14 @@
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
-import android.graphics.Rect;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.impl.CameraControlInternal;
import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.Config;
-import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.ForwardingCameraControl;
import androidx.camera.core.impl.utils.futures.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -42,83 +38,20 @@
* A {@link CameraControlInternal} that is used to control the virtual camera.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class VirtualCameraControl implements CameraControlInternal {
+public class VirtualCameraControl extends ForwardingCameraControl {
private static final int DEFAULT_JPEG_QUALITY = 100;
- private final CameraControlInternal mParent;
private final StreamSharing.Control mStreamSharingControl;
VirtualCameraControl(@NonNull CameraControlInternal parent,
@NonNull StreamSharing.Control streamSharingControl) {
- mParent = parent;
+ super(parent);
mStreamSharingControl = streamSharingControl;
}
@NonNull
@Override
- public ListenableFuture<Void> enableTorch(boolean torch) {
- return mParent.enableTorch(torch);
- }
-
- @NonNull
- @Override
- public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
- @NonNull FocusMeteringAction action) {
- return mParent.startFocusAndMetering(action);
- }
-
- @NonNull
- @Override
- public ListenableFuture<Void> cancelFocusAndMetering() {
- return mParent.cancelFocusAndMetering();
- }
-
- @NonNull
- @Override
- public ListenableFuture<Void> setZoomRatio(float ratio) {
- return mParent.setZoomRatio(ratio);
- }
-
- @NonNull
- @Override
- public ListenableFuture<Void> setLinearZoom(float linearZoom) {
- return mParent.setLinearZoom(linearZoom);
- }
-
- @NonNull
- @Override
- public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
- return mParent.setExposureCompensationIndex(value);
- }
-
- @Override
- public int getFlashMode() {
- return mParent.getFlashMode();
- }
-
- @Override
- public void setFlashMode(int flashMode) {
- mParent.setFlashMode(flashMode);
- }
-
- @Override
- public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
- mParent.addZslConfig(sessionConfigBuilder);
- }
-
- @Override
- public void setZslDisabledByUserCaseConfig(boolean disabled) {
- mParent.setZslDisabledByUserCaseConfig(disabled);
- }
-
- @Override
- public boolean isZslDisabledByByUserCaseConfig() {
- return mParent.isZslDisabledByByUserCaseConfig();
- }
-
- @NonNull
- @Override
public ListenableFuture<List<Void>> submitStillCaptureRequests(
@NonNull List<CaptureConfig> captureConfigs,
@ImageCapture.CaptureMode int captureMode,
@@ -132,32 +65,4 @@
return requireNonNull(captureConfig.getImplementationOptions().retrieveOption(
CaptureConfig.OPTION_JPEG_QUALITY, DEFAULT_JPEG_QUALITY));
}
-
- @NonNull
- @Override
- public SessionConfig getSessionConfig() {
- return mParent.getSessionConfig();
- }
-
- @NonNull
- @Override
- public Rect getSensorRect() {
- return mParent.getSensorRect();
- }
-
- @Override
- public void addInteropConfig(@NonNull Config config) {
- mParent.addInteropConfig(config);
- }
-
- @Override
- public void clearInteropConfig() {
- mParent.clearInteropConfig();
- }
-
- @NonNull
- @Override
- public Config getInteropConfig() {
- return mParent.getInteropConfig();
- }
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
index c20d759..1f7200f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
@@ -23,6 +23,7 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.internal.DoNotInstrument;
+import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
@@ -311,13 +312,53 @@
}
@Test(expected = IllegalArgumentException.class)
- public void builderWithNullPoint() {
- new FocusMeteringAction.Builder(null).build();
- }
-
- @Test(expected = IllegalArgumentException.class)
public void builderWithNullPoint2() {
new FocusMeteringAction.Builder(null, FocusMeteringAction.FLAG_AF).build();
}
+ @Test
+ public void copyBuilder() {
+ // 1. Arrange
+ FocusMeteringAction action1 = new FocusMeteringAction.Builder(mPoint1)
+ .addPoint(mPoint2, FocusMeteringAction.FLAG_AE)
+ .setAutoCancelDuration(8000, TimeUnit.MILLISECONDS)
+ .build();
+
+ // 2. Act
+ FocusMeteringAction action2 = new FocusMeteringAction.Builder(action1).build();
+
+ // 3. Assert
+ assertThat(action1.getMeteringPointsAf()).containsExactlyElementsIn(
+ action2.getMeteringPointsAf()
+ );
+ assertThat(action1.getMeteringPointsAe()).containsExactlyElementsIn(
+ action2.getMeteringPointsAe()
+ );
+ assertThat(action1.getMeteringPointsAwb()).containsExactlyElementsIn(
+ action2.getMeteringPointsAwb()
+ );
+ assertThat(action1.getAutoCancelDurationInMillis())
+ .isEqualTo(action2.getAutoCancelDurationInMillis());
+ assertThat(action1.isAutoCancelEnabled()).isEqualTo(action2.isAutoCancelEnabled());
+ }
+
+ @Test
+ public void removePoints() {
+ // 1. Arrange
+ FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(mPoint1)
+ .addPoint(mPoint2, FocusMeteringAction.FLAG_AE);
+
+ // 2. Act
+ FocusMeteringAction action = builder.removePoints(FocusMeteringAction.FLAG_AE).build();
+
+ // 3. Assert
+ assertThat(action.getMeteringPointsAe()).isEmpty();
+ assertThat(action.getMeteringPointsAf()).containsExactlyElementsIn(
+ Arrays.asList(mPoint1)
+ );
+ assertThat(action.getMeteringPointsAwb()).containsExactlyElementsIn(
+ Arrays.asList(mPoint1)
+ );
+ }
+
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index cabf0cc..670ef68 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -20,15 +20,22 @@
import android.graphics.Matrix
import android.graphics.Rect
import android.os.Build
+import android.util.Range
import android.util.Rational
import android.util.Size
import android.view.Surface
import androidx.camera.core.CameraEffect
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.FocusMeteringAction.FLAG_AE
+import androidx.camera.core.FocusMeteringAction.FLAG_AF
+import androidx.camera.core.FocusMeteringAction.FLAG_AWB
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory
+import androidx.camera.core.TorchState
import androidx.camera.core.UseCase
import androidx.camera.core.ViewPort
import androidx.camera.core.concurrent.CameraCoordinator
@@ -38,6 +45,8 @@
import androidx.camera.core.impl.Identifier
import androidx.camera.core.impl.MutableOptionsBundle
import androidx.camera.core.impl.OptionsBundle
+import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.UseCaseConfigFactory
import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -45,17 +54,23 @@
import androidx.camera.core.processing.DefaultSurfaceProcessor
import androidx.camera.core.streamsharing.StreamSharing
import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraControl
import androidx.camera.testing.fakes.FakeCameraCoordinator
import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.fakes.FakeSessionProcessor
import androidx.camera.testing.fakes.FakeSurfaceEffect
import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
import androidx.camera.testing.fakes.FakeUseCase
import androidx.camera.testing.fakes.FakeUseCaseConfig
import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
import androidx.camera.testing.fakes.GrayscaleImageEffect
+import androidx.concurrent.futures.await
+import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
+import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -81,7 +96,6 @@
instrumentedPackages = ["androidx.camera.core"]
)
class CameraUseCaseAdapterTest {
-
private lateinit var effects: List<CameraEffect>
private lateinit var executor: ExecutorService
@@ -93,6 +107,8 @@
private lateinit var sharedEffect: FakeSurfaceEffect
private lateinit var cameraCoordinator: CameraCoordinator
private lateinit var surfaceProcessorInternal: FakeSurfaceProcessorInternal
+ private lateinit var fakeCameraControl: FakeCameraControl
+ private lateinit var fakeCameraInfo: FakeCameraInfoInternal
private val fakeCameraSet = LinkedHashSet<CameraInternal>()
private val imageEffect = GrayscaleImageEffect()
private val preview = Preview.Builder().build()
@@ -106,7 +122,9 @@
@Before
fun setUp() {
fakeCameraDeviceSurfaceManager = FakeCameraDeviceSurfaceManager()
- fakeCamera = FakeCamera(CAMERA_ID)
+ fakeCameraControl = FakeCameraControl()
+ fakeCameraInfo = FakeCameraInfoInternal()
+ fakeCamera = FakeCamera(CAMERA_ID, fakeCameraControl, fakeCameraInfo)
cameraCoordinator = FakeCameraCoordinator()
useCaseConfigFactory = FakeUseCaseConfigFactory()
fakeCameraSet.add(fakeCamera)
@@ -209,7 +227,7 @@
fun invalidUseCaseComboCantBeFixedByStreamSharing_throwsException() {
// Arrange: create a camera that only support one JPEG stream.
fakeCameraDeviceSurfaceManager.setValidSurfaceCombos(setOf(listOf(JPEG)))
- // Act: add PRIV and JPEG streams.
+ // Act: add PRIVATE and JPEG streams.
adapter.addUseCases(setOf(preview, image))
}
@@ -909,6 +927,316 @@
assertThat(video.effect).isNull()
}
+ private fun createAdapterWithSupportedCameraOperations(
+ @RestrictedCameraControl.CameraOperation supportedOps: Set<Int>
+ ): CameraUseCaseAdapter {
+ val cameraUseCaseAdapter = CameraUseCaseAdapter(
+ fakeCameraSet,
+ cameraCoordinator,
+ fakeCameraDeviceSurfaceManager,
+ useCaseConfigFactory
+ )
+
+ val fakeSessionProcessor = FakeSessionProcessor()
+ // no camera operations are supported.
+ fakeSessionProcessor.restrictedCameraOperations = supportedOps
+ val cameraConfig: CameraConfig = FakeCameraConfig(fakeSessionProcessor)
+ cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
+ return cameraUseCaseAdapter
+ }
+
+ @Test
+ fun cameraControlFailed_whenNoCameraOperationsSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(supportedOps = emptySet())
+
+ // 2. Act && Assert
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.setZoomRatio(1.0f).await()
+ }
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.setLinearZoom(1.0f).await()
+ }
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.enableTorch(true).await()
+ }
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+ getFocusMeteringAction()
+ ).await()
+ }
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.setExposureCompensationIndex(0).await()
+ }
+ }
+
+ private fun getFocusMeteringAction(
+ meteringMode: Int = FLAG_AF or FLAG_AE or FLAG_AWB
+ ): FocusMeteringAction {
+ val pointFactory = SurfaceOrientedMeteringPointFactory(1f, 1f)
+ return FocusMeteringAction.Builder(
+ pointFactory.createPoint(0.5f, 0.5f), meteringMode)
+ .build()
+ }
+
+ @Test
+ fun zoomEnabled_whenZoomOperationsSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.ZOOM))
+
+ // 2. Act && Assert
+ cameraUseCaseAdapter.cameraControl.setZoomRatio(2.0f).await()
+ assertThat(fakeCameraControl.zoomRatio).isEqualTo(2.0f)
+ cameraUseCaseAdapter.cameraControl.setLinearZoom(1.0f).await()
+ assertThat(fakeCameraControl.linearZoom).isEqualTo(1.0f)
+ }
+
+ @Test
+ fun torchEnabled_whenTorchOperationSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.TORCH))
+
+ // 2. Act
+ cameraUseCaseAdapter.cameraControl.enableTorch(true).await()
+
+ // 3. Assert
+ assertThat(fakeCameraControl.torchEnabled).isEqualTo(true)
+ }
+
+ @Test
+ fun focusMetering_afEnabled_whenAfOperationSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.AUTO_FOCUS,
+ RestrictedCameraControl.AF_REGION,
+ ))
+
+ // 2. Act
+ cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+ getFocusMeteringAction()
+ ).await()
+
+ // 3. Assert
+ // Only AF point remains, AE/AWB points removed.
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf?.size)
+ .isEqualTo(1)
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe)
+ .isEmpty()
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb)
+ .isEmpty()
+ }
+
+ @Test
+ fun focusMetering_aeEnabled_whenAeOperationsSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.AE_REGION,
+ ))
+
+ // 2. Act
+ cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+ getFocusMeteringAction()
+ ).await()
+
+ // 3. Assert
+ // Only AE point remains, AF/AWB points removed.
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe?.size)
+ .isEqualTo(1)
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf)
+ .isEmpty()
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb)
+ .isEmpty()
+ }
+
+ @Test
+ fun focusMetering_awbEnabled_whenAwbOperationsSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.AWB_REGION,
+ ))
+
+ // 2. Act
+ cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+ getFocusMeteringAction()
+ ).await()
+
+ // 3. Assert
+ // Only AWB point remains, AF/AE points removed.
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb?.size)
+ .isEqualTo(1)
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf)
+ .isEmpty()
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe)
+ .isEmpty()
+ }
+
+ @Test
+ fun focusMetering_disabled_whenNoneIsSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.AE_REGION,
+ ))
+
+ // 2. Act && Assert
+ assertThrows<IllegalStateException> {
+ cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+ getFocusMeteringAction(FLAG_AF or FLAG_AWB)
+ ).await()
+ }
+ assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction).isNull()
+ }
+
+ @Test
+ fun exposureEnabled_whenExposureOperationSupported(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.EXPOSURE_COMPENSATION))
+
+ // 2. Act
+ cameraUseCaseAdapter.cameraControl.setExposureCompensationIndex(0).await()
+
+ // 3. Assert
+ assertThat(fakeCameraControl.exposureCompensationIndex).isEqualTo(0)
+ }
+
+ @Test
+ fun cameraInfo_returnsDisabledState_AllOpsDisabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = emptySet())
+
+ // 2. Act && Assert
+ // Zoom is disabled
+ val zoomState = cameraUseCaseAdapter.cameraInfo.zoomState.value!!
+ assertThat(zoomState.minZoomRatio).isEqualTo(1f)
+ assertThat(zoomState.maxZoomRatio).isEqualTo(1f)
+ assertThat(zoomState.zoomRatio).isEqualTo(1f)
+ assertThat(zoomState.linearZoom).isEqualTo(0f)
+
+ // Flash is disabled
+ assertThat(cameraUseCaseAdapter.cameraInfo.hasFlashUnit()).isFalse()
+
+ // Torch is disabled.
+ assertThat(cameraUseCaseAdapter.cameraInfo.torchState.value).isEqualTo(TorchState.OFF)
+
+ // FocusMetering is disabled.
+ assertThat(cameraUseCaseAdapter.cameraInfo
+ .isFocusMeteringSupported(getFocusMeteringAction()))
+ .isFalse()
+
+ // ExposureCompensation is disabled.
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.isExposureCompensationSupported)
+ .isFalse()
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationRange)
+ .isEqualTo(Range(0, 0))
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationStep)
+ .isEqualTo(Rational.ZERO)
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationIndex)
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun cameraInfo_zoomEnabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.ZOOM)
+ )
+ fakeCameraInfo.setZoom(10f, 0.6f, 10f, 1f)
+
+ // 2. Act
+ val zoomState = cameraUseCaseAdapter.cameraInfo.zoomState.value!!
+
+ // 3. Assert
+ val fakeZoomState = fakeCameraInfo.zoomState.value!!
+ assertThat(zoomState.zoomRatio).isEqualTo(fakeZoomState.zoomRatio)
+ assertThat(zoomState.minZoomRatio).isEqualTo(fakeZoomState.minZoomRatio)
+ assertThat(zoomState.maxZoomRatio).isEqualTo(fakeZoomState.maxZoomRatio)
+ assertThat(zoomState.linearZoom).isEqualTo(fakeZoomState.linearZoom)
+ }
+
+ @Test
+ fun cameraInfo_torchEnabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.TORCH)
+ )
+ fakeCameraInfo.setTorch(TorchState.ON)
+
+ // 2. Act && Assert
+ assertThat(cameraUseCaseAdapter.cameraInfo.torchState.value)
+ .isEqualTo(fakeCameraInfo.torchState.value)
+ }
+
+ @Test
+ fun cameraInfo_afEnabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.AUTO_FOCUS,
+ RestrictedCameraControl.AF_REGION
+ )
+ )
+ fakeCameraInfo.setIsFocusMeteringSupported(true)
+
+ // 2. Act && Assert
+ assertThat(cameraUseCaseAdapter.cameraInfo.isFocusMeteringSupported(
+ getFocusMeteringAction()
+ )).isTrue()
+ }
+
+ @Test
+ fun cameraInfo_exposureExposureEnabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(
+ RestrictedCameraControl.EXPOSURE_COMPENSATION,
+ )
+ )
+ fakeCameraInfo.setExposureState(2, Range.create(0, 10), Rational(1, 1), true)
+
+ // 2. Act && Assert
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationIndex)
+ .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationIndex)
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationRange)
+ .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationRange)
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationStep)
+ .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationStep)
+ assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.isExposureCompensationSupported)
+ .isEqualTo(fakeCameraInfo.exposureState.isExposureCompensationSupported)
+ }
+
+ @Test
+ fun cameraInfo_flashEnabled(): Unit = runBlocking {
+ // 1. Arrange
+ val cameraUseCaseAdapter =
+ createAdapterWithSupportedCameraOperations(
+ supportedOps = setOf(RestrictedCameraControl.FLASH)
+ )
+
+ // 2. Act && Assert
+ assertThat(cameraUseCaseAdapter.cameraInfo.hasFlashUnit())
+ .isEqualTo(fakeCameraInfo.hasFlashUnit())
+ }
+
private fun createCoexistingRequiredRuleCameraConfig(): CameraConfig {
return object : CameraConfig {
private val mUseCaseConfigFactory =
@@ -950,7 +1278,9 @@
return false
}
- private class FakeCameraConfig : CameraConfig {
+ private class FakeCameraConfig(
+ val sessionProcessor: FakeSessionProcessor? = null
+ ) : CameraConfig {
private val mUseCaseConfigFactory =
UseCaseConfigFactory { _, _ -> null }
private val mIdentifier = Identifier.create(Any())
@@ -965,5 +1295,13 @@
override fun getConfig(): Config {
return OptionsBundle.emptyBundle()
}
+
+ override fun getSessionProcessor(valueIfMissing: SessionProcessor?): SessionProcessor? {
+ return sessionProcessor ?: valueIfMissing
+ }
+
+ override fun getSessionProcessor(): SessionProcessor {
+ return sessionProcessor!!
+ }
}
}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index 0e76994..cfa06c8 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -204,7 +204,8 @@
@Test
fun canInvokeStartTrigger() = runBlocking {
val fakeSessionProcessImpl = FakeSessionProcessImpl()
- val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl, context)
+ val advancedSessionProcessor = AdvancedSessionProcessor(
+ fakeSessionProcessImpl, emptyList(), context)
val parametersMap: MutableMap<CaptureRequest.Key<*>, Any> = mutableMapOf(
CaptureRequest.CONTROL_AF_MODE to CaptureRequest.CONTROL_AF_MODE_AUTO,
@@ -375,7 +376,8 @@
imageCapture: ImageCapture,
imageAnalysis: ImageAnalysis? = null
) {
- val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl, context)
+ val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
+ emptyList(), context)
val latchPreviewFrame = CountDownLatch(1)
val latchAnalysis = CountDownLatch(1)
val deferCapturedImage = CompletableDeferred<ImageProxy>()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index f9e1b5a..288929c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -154,7 +154,7 @@
fakePreviewExtenderImpl = FakePreviewExtenderImpl(previewProcessorType)
fakeCaptureExtenderImpl = FakeImageCaptureExtenderImpl(hasCaptureProcessor)
basicExtenderSessionProcessor = BasicExtenderSessionProcessor(
- fakePreviewExtenderImpl, fakeCaptureExtenderImpl, context
+ fakePreviewExtenderImpl, fakeCaptureExtenderImpl, emptyList(), context
)
}
@@ -205,7 +205,7 @@
hasCaptureProcessor, throwErrorOnProcess = true
)
basicExtenderSessionProcessor = BasicExtenderSessionProcessor(
- fakePreviewExtenderImpl, fakeCaptureExtenderImpl, context
+ fakePreviewExtenderImpl, fakeCaptureExtenderImpl, emptyList(), context
)
val preview = Preview.Builder().build()
val imageCapture = ImageCapture.Builder().build()
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
index 17a7a78..5722a6b 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
@@ -27,9 +28,11 @@
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.interop.Camera2CameraInfo;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraInfo;
+import androidx.camera.core.Logger;
import androidx.camera.core.impl.SessionProcessor;
import androidx.camera.extensions.ExtensionMode;
import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
@@ -52,6 +55,7 @@
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class AdvancedVendorExtender implements VendorExtender {
+ private static final String TAG = "AdvancedVendorExtender";
private final ExtensionDisabledValidator mExtensionDisabledValidator =
new ExtensionDisabledValidator();
private final AdvancedExtenderImpl mAdvancedExtenderImpl;
@@ -84,6 +88,11 @@
}
}
+ @VisibleForTesting
+ AdvancedVendorExtender(AdvancedExtenderImpl advancedExtenderImpl) {
+ mAdvancedExtenderImpl = advancedExtenderImpl;
+ }
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
@Override
public void init(@NonNull CameraInfo cameraInfo) {
@@ -151,11 +160,28 @@
return yuvList == null ? new Size[0] : yuvList.toArray(new Size[0]);
}
+ @NonNull
+ private List<CaptureRequest.Key> getSupportedParameterKeys() {
+ List<CaptureRequest.Key> keys = Collections.emptyList();
+ if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_3) >= 0) {
+ try {
+ keys = Collections.unmodifiableList(
+ mAdvancedExtenderImpl.getAvailableCaptureRequestKeys());
+ } catch (Exception e) {
+ Logger.e(TAG, "AdvancedExtenderImpl.getAvailableCaptureRequestKeys "
+ + "throws exceptions", e);
+ }
+ }
+ return keys;
+ }
+
@Nullable
@Override
public SessionProcessor createSessionProcessor(@NonNull Context context) {
Preconditions.checkNotNull(mCameraId, "VendorExtender#init() must be called first");
return new AdvancedSessionProcessor(
- mAdvancedExtenderImpl.createSessionProcessor(), context);
+ mAdvancedExtenderImpl.createSessionProcessor(),
+ getSupportedParameterKeys(),
+ context);
}
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index 2a7ed76..61f0550 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -19,7 +19,9 @@
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Build;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
@@ -28,6 +30,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.interop.Camera2CameraInfo;
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.CameraInfo;
@@ -46,14 +49,14 @@
import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl;
import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.internal.compat.workaround.AvailableKeysRetriever;
import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
import androidx.camera.extensions.internal.sessionprocessor.BasicExtenderSessionProcessor;
import androidx.core.util.Preconditions;
-import org.jetbrains.annotations.TestOnly;
-
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -69,6 +72,27 @@
private PreviewExtenderImpl mPreviewExtenderImpl = null;
private ImageCaptureExtenderImpl mImageCaptureExtenderImpl = null;
private CameraInfo mCameraInfo;
+ private String mCameraId;
+ private CameraCharacteristics mCameraCharacteristics;
+ private AvailableKeysRetriever mAvailableKeysRetriever = new AvailableKeysRetriever();
+
+ static final List<CaptureRequest.Key> sBaseSupportedKeys = new ArrayList<>(Arrays.asList(
+ CaptureRequest.SCALER_CROP_REGION,
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_REGIONS,
+ CaptureRequest.CONTROL_AE_REGIONS,
+ CaptureRequest.CONTROL_AWB_REGIONS,
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.FLASH_MODE,
+ CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
+ ));
+ static {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ sBaseSupportedKeys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
+ }
+ }
public BasicVendorExtender(@ExtensionMode.Mode int mode) {
try {
@@ -102,7 +126,7 @@
}
}
- @TestOnly
+ @VisibleForTesting
BasicVendorExtender(ImageCaptureExtenderImpl imageCaptureExtenderImpl,
PreviewExtenderImpl previewExtenderImpl) {
mPreviewExtenderImpl = previewExtenderImpl;
@@ -136,11 +160,11 @@
return;
}
- String cameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
- CameraCharacteristics cameraCharacteristics =
+ mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
+ mCameraCharacteristics =
Camera2CameraInfo.extractCameraCharacteristics(cameraInfo);
- mPreviewExtenderImpl.init(cameraId, cameraCharacteristics);
- mImageCaptureExtenderImpl.init(cameraId, cameraCharacteristics);
+ mPreviewExtenderImpl.init(mCameraId, mCameraCharacteristics);
+ mImageCaptureExtenderImpl.init(mCameraId, mCameraCharacteristics);
Logger.d(TAG, "PreviewExtender processorType= " + mPreviewExtenderImpl.getProcessorType());
Logger.d(TAG, "ImageCaptureExtender processor= "
@@ -285,11 +309,41 @@
return getOutputSizes(ImageFormat.YUV_420_888);
}
+ @NonNull
+ private List<CaptureRequest.Key> getSupportedParameterKeys(Context context) {
+ if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_3) >= 0) {
+ try {
+ List<CaptureRequest.Key> keys =
+ Collections.unmodifiableList(
+ mAvailableKeysRetriever.getAvailableCaptureRequestKeys(
+ mImageCaptureExtenderImpl,
+ mCameraId,
+ mCameraCharacteristics,
+ context));
+ if (keys == null) {
+ keys = Collections.emptyList();
+ }
+ return keys;
+ } catch (Exception e) {
+ // it could crash on some OEMs.
+ Logger.e(TAG, "ImageCaptureExtenderImpl.getAvailableCaptureRequestKeys "
+ + "throws exceptions", e);
+ return Collections.emptyList();
+ }
+ } else {
+ // For Basic Extender implementing v1.2 or below, we assume zoom/tap-to-focus/flash/EC
+ // are supported for compatibility reason.
+ return Collections.unmodifiableList(sBaseSupportedKeys);
+ }
+ }
+
@Nullable
@Override
public SessionProcessor createSessionProcessor(@NonNull Context context) {
Preconditions.checkNotNull(mCameraInfo, "VendorExtender#init() must be called first");
- return new BasicExtenderSessionProcessor(mPreviewExtenderImpl, mImageCaptureExtenderImpl,
+ return new BasicExtenderSessionProcessor(
+ mPreviewExtenderImpl, mImageCaptureExtenderImpl,
+ getSupportedParameterKeys(context),
context);
}
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
index ec3f327..52af0b8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
@@ -22,6 +22,8 @@
import androidx.camera.core.Logger;
import androidx.camera.extensions.impl.ExtensionVersionImpl;
+import org.jetbrains.annotations.TestOnly;
+
/**
* Provides interfaces to check the extension version.
*/
@@ -31,6 +33,15 @@
private static volatile ExtensionVersion sExtensionVersion;
+ /**
+ * For testing only. Inject a fake {@link ExtensionVersion}. Set it to {@code null} to unset
+ * it.
+ */
+ @TestOnly
+ public static void injectInstance(@Nullable ExtensionVersion extensionVersion) {
+ sExtensionVersion = extensionVersion;
+ }
+
private static ExtensionVersion getInstance() {
if (sExtensionVersion != null) {
return sExtensionVersion;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
index 82bb2e7..532e1e8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
@@ -48,6 +48,10 @@
quirks.add(new CrashWhenOnDisableTooSoon());
}
+ if (GetAvailableKeysNeedsOnInit.load()) {
+ quirks.add(new GetAvailableKeysNeedsOnInit());
+ }
+
return quirks;
}
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java
new file mode 100644
index 0000000..57f0e76
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java
@@ -0,0 +1,38 @@
+/*
+ * 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.extensions.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * <p>QuirkSummary
+ * Bug Id: b/279541627,
+ * Description: ImageCaptureExtenderImpl.getAvailableCaptureRequestKeys and
+ * getAvailableCaptureResultKeys incorrectly expect onInit() to be invoked to supply the
+ * CameraCharacteristics. It causes a {@link NullPointerException} if onInit() is not invoked in
+ * prior to getAvailableCaptureRequestKeys or getAvailableCaptureResultKeys.
+ * Device(s): All Samsung devices
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class GetAvailableKeysNeedsOnInit implements Quirk {
+ static boolean load() {
+ return Build.BRAND.equalsIgnoreCase("SAMSUNG");
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java
new file mode 100644
index 0000000..605719e
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java
@@ -0,0 +1,68 @@
+/*
+ * 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.extensions.internal.compat.workaround;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
+import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.extensions.internal.compat.quirk.GetAvailableKeysNeedsOnInit;
+
+import java.util.List;
+
+/**
+ * A workaround for getting the available CaptureRequest keys safely.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class AvailableKeysRetriever {
+ boolean mShouldInvokeOnInit;
+
+ /**
+ * Default constructor.
+ */
+ public AvailableKeysRetriever() {
+ mShouldInvokeOnInit = DeviceQuirks.get(GetAvailableKeysNeedsOnInit.class) != null;
+ }
+
+ /**
+ * Get available CaptureRequest keys from the given {@link ImageCaptureExtenderImpl}. The
+ * cameraId, cameraCharacteristics and the context is needed for invoking onInit whenever
+ * necessary.
+ */
+ @NonNull
+ public List<CaptureRequest.Key> getAvailableCaptureRequestKeys(
+ @NonNull ImageCaptureExtenderImpl imageCaptureExtender,
+ @NonNull String cameraId,
+ @NonNull CameraCharacteristics cameraCharacteristics,
+ @NonNull Context context) {
+ if (mShouldInvokeOnInit) {
+ imageCaptureExtender.onInit(cameraId, cameraCharacteristics, context);
+ }
+
+ try {
+ return imageCaptureExtender.getAvailableCaptureRequestKeys();
+ } finally {
+ if (mShouldInvokeOnInit) {
+ imageCaptureExtender.onDeInit();
+ }
+ }
+ }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
index ac7b358..ab42134 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
@@ -62,7 +62,10 @@
private final SessionProcessorImpl mImpl;
private final Context mContext;
- public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl, @NonNull Context context) {
+ public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl,
+ @NonNull List<CaptureRequest.Key> supportedKeys,
+ @NonNull Context context) {
+ super(supportedKeys);
mImpl = impl;
mContext = context;
}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
index 5452a8a..4a282ab 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
@@ -96,7 +96,9 @@
public BasicExtenderSessionProcessor(@NonNull PreviewExtenderImpl previewExtenderImpl,
@NonNull ImageCaptureExtenderImpl imageCaptureExtenderImpl,
+ @NonNull List<CaptureRequest.Key> supportedKeys,
@NonNull Context context) {
+ super(supportedKeys);
mPreviewExtenderImpl = previewExtenderImpl;
mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
mContext = context;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
index a10ddcb..f02f5fa 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
@@ -20,6 +20,7 @@
import android.hardware.camera2.CaptureRequest;
import android.media.Image;
import android.media.ImageReader;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -36,15 +37,20 @@
import androidx.camera.core.Logger;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.OutputSurface;
+import androidx.camera.core.impl.RestrictedCameraControl;
+import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.SessionProcessor;
import androidx.camera.core.impl.SessionProcessorSurface;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Base class for SessionProcessor implementation. It is responsible for creating image readers and
@@ -67,6 +73,67 @@
private String mCameraId;
@NonNull
+
+ private final @CameraOperation Set<Integer> mSupportedCameraOperations;
+
+ SessionProcessorBase(@NonNull List<CaptureRequest.Key> supportedParameterKeys) {
+ mSupportedCameraOperations = getSupportedCameraOperations(supportedParameterKeys);
+ }
+
+ private @CameraOperation Set<Integer> getSupportedCameraOperations(
+ @NonNull List<CaptureRequest.Key> supportedParameterKeys) {
+ @CameraOperation Set<Integer> operations = new HashSet<>();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ if (supportedParameterKeys.contains(CaptureRequest.CONTROL_ZOOM_RATIO)
+ || supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
+ operations.add(RestrictedCameraControl.ZOOM);
+ }
+ } else {
+ if (supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
+ operations.add(RestrictedCameraControl.ZOOM);
+ }
+ }
+
+ if (supportedParameterKeys.containsAll(
+ Arrays.asList(
+ CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_MODE))) {
+ operations.add(RestrictedCameraControl.AUTO_FOCUS);
+ }
+
+ if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AF_REGIONS)) {
+ operations.add(RestrictedCameraControl.AF_REGION);
+ }
+
+ if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_REGIONS)) {
+ operations.add(RestrictedCameraControl.AE_REGION);
+ }
+
+ if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AWB_REGIONS)) {
+ operations.add(RestrictedCameraControl.AWB_REGION);
+ }
+
+ if (supportedParameterKeys.containsAll(
+ Arrays.asList(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER))) {
+ operations.add(RestrictedCameraControl.FLASH);
+ }
+
+ if (supportedParameterKeys.containsAll(
+ Arrays.asList(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.FLASH_MODE))) {
+ operations.add(RestrictedCameraControl.TORCH);
+ }
+
+ if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION)) {
+ operations.add(RestrictedCameraControl.EXPOSURE_COMPENSATION);
+ }
+ return operations;
+ }
+
+ @NonNull
private static SessionProcessorSurface createOutputConfigSurface(
@NonNull Camera2OutputConfig outputConfig, Map<Integer, ImageReader> imageReaderMap) {
if (outputConfig instanceof SurfaceOutputConfig) {
@@ -97,6 +164,7 @@
}
throw new UnsupportedOperationException("Unsupported Camera2OutputConfig:" + outputConfig);
}
+
@NonNull
@Override
@OptIn(markerClass = ExperimentalCamera2Interop.class)
@@ -163,6 +231,12 @@
}
@NonNull
+ @Override
+ public @CameraOperation Set<Integer> getSupportedCameraOperations() {
+ return mSupportedCameraOperations;
+ }
+
+ @NonNull
protected abstract Camera2SessionConfig initSessionInternal(
@NonNull String cameraId,
@NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
new file mode 100644
index 0000000..0efc40a
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
@@ -0,0 +1,457 @@
+/*
+ * 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.extensions.internal
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.SessionConfiguration
+import android.os.Build
+import android.util.Pair
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.internal.Camera2CameraInfoImpl
+import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.extensions.impl.CaptureStageImpl
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
+import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
+import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
+import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+ minSdk = Build.VERSION_CODES.LOLLIPOP,
+ instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class SupportedCameraOperationsTest(
+ private val extenderType: String
+) {
+ val context = RuntimeEnvironment.getApplication()
+
+ companion object {
+ @JvmStatic
+ @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
+ fun createTestSet(): List<String> {
+ return listOf("basic", "advanced")
+ }
+ }
+ private fun setCameraXExtensionsVersion(version: String) {
+ val field = VersionName::class.java.getDeclaredField("CURRENT")
+ field.isAccessible = true
+ field[null] = VersionName(version)
+ }
+
+ private fun setExtensionRuntimeVersion(version: String) {
+ ExtensionVersion.injectInstance(object : ExtensionVersion() {
+ override fun isAdvancedExtenderSupportedInternal(): Boolean {
+ return false
+ }
+
+ override fun getVersionObject(): Version {
+ return Version.parse(version)!!
+ }
+ })
+ }
+
+ @Before
+ fun setUp() {
+ setupCameraCharacteristics()
+ setCameraXExtensionsVersion("1.3.0")
+ setExtensionRuntimeVersion("1.3.0")
+ }
+
+ private fun setupCameraCharacteristics() {
+ val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+ val shadowCharacteristics = Shadow.extract<ShadowCameraCharacteristics>(characteristics)
+ shadowCharacteristics.set(
+ CameraCharacteristics.LENS_FACING, CameraCharacteristics.LENS_FACING_BACK
+ )
+ shadowCharacteristics.set(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, arrayOf(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
+ )
+ )
+ val cameraManager = ApplicationProvider.getApplicationContext<Context>()
+ .getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ (Shadow.extract<Any>(cameraManager) as ShadowCameraManager)
+ .addCamera("0", characteristics)
+ }
+
+ private fun testSupportedCameraOperation(
+ supportedCaptureRequestKeys: List<CaptureRequest.Key<out Any>>,
+ @RestrictedCameraControl.CameraOperation expectSupportedOperations: Set<Int>
+ ) {
+ var vendorExtender: VendorExtender? = null
+ if (extenderType == "basic") {
+ val fakeImageCaptureExtenderImpl = FakeImageCaptureExtenderImpl(
+ supportedRequestKeys = supportedCaptureRequestKeys
+ )
+ vendorExtender = BasicVendorExtender(fakeImageCaptureExtenderImpl, null)
+ } else if (extenderType == "advanced") {
+ val fakeAdvancedExtenderImpl = FakeAdvancedVendorExtenderImpl(
+ supportedRequestKeys = supportedCaptureRequestKeys
+ )
+ vendorExtender = AdvancedVendorExtender(fakeAdvancedExtenderImpl)
+ }
+
+ val cameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+ vendorExtender!!.init(cameraInfo)
+ val sessionProcessor = vendorExtender.createSessionProcessor(context)!!
+ assertThat(sessionProcessor.supportedCameraOperations)
+ .containsExactlyElementsIn(expectSupportedOperations)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.R)
+ @Test
+ fun supportedCameraOperations_zoomIsEnabled_androidR() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_ZOOM_RATIO
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.ZOOM
+ )
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.R)
+ @Test
+ fun supportedCameraOperations_cropregion_zoomIsEnabled_androidR() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.SCALER_CROP_REGION
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.ZOOM
+ )
+ )
+ }
+
+ @Config(maxSdk = Build.VERSION_CODES.Q)
+ @Test
+ fun supportedCameraOperations_zoomIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.SCALER_CROP_REGION
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.ZOOM
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_autoFocusIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_TRIGGER
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.AUTO_FOCUS
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_afRegionIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AF_REGIONS,
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.AF_REGION
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_aeRegionIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AE_REGIONS,
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.AE_REGION
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_awbRegionIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AWB_REGIONS,
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.AWB_REGION
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_torchIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.FLASH_MODE
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.TORCH
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_flashIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.FLASH
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_exposureCompensationIsEnabled() {
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
+ ),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.EXPOSURE_COMPENSATION
+ )
+ )
+ }
+
+ // For Basic extender under 1.3.0, ensures all operations are supported
+ @Test
+ fun supportedCameraOperations_allOperationsEnabled_basic1_2_and_below() {
+ assumeTrue(extenderType == "basic")
+ setExtensionRuntimeVersion("1.2.0")
+ setCameraXExtensionsVersion("1.3.0")
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = emptyList(),
+ expectSupportedOperations = setOf(
+ RestrictedCameraControl.ZOOM,
+ RestrictedCameraControl.AUTO_FOCUS,
+ RestrictedCameraControl.TORCH,
+ RestrictedCameraControl.AF_REGION,
+ RestrictedCameraControl.AE_REGION,
+ RestrictedCameraControl.AWB_REGION,
+ RestrictedCameraControl.EXPOSURE_COMPENSATION,
+ RestrictedCameraControl.FLASH,
+ )
+ )
+ }
+
+ @Test
+ fun supportedCameraOperations_allOperationsDisabled_advanced1_2_and_below() {
+ assumeTrue(extenderType == "advanced")
+ setExtensionRuntimeVersion("1.2.0")
+ setCameraXExtensionsVersion("1.3.0")
+ testSupportedCameraOperation(
+ supportedCaptureRequestKeys = listOf(
+ CaptureRequest.SCALER_CROP_REGION,
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_REGIONS,
+ CaptureRequest.CONTROL_AE_REGIONS,
+ CaptureRequest.CONTROL_AWB_REGIONS,
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.FLASH_MODE,
+ CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
+ ),
+ expectSupportedOperations = emptySet() // No ops should be supported.
+ )
+ }
+
+ private class FakeImageCaptureExtenderImpl(
+ val supportedRequestKeys: List<CaptureRequest.Key<out Any>>
+ ) : ImageCaptureExtenderImpl {
+ override fun isExtensionAvailable(
+ cameraId: String,
+ cameraCharacteristics: CameraCharacteristics
+ ): Boolean = true
+ override fun init(cameraId: String, cameraCharacteristics: CameraCharacteristics) {
+ }
+ override fun getCaptureProcessor() = null
+ override fun getCaptureStages(): List<CaptureStageImpl> = emptyList()
+ override fun getMaxCaptureStage() = 2
+ override fun getSupportedResolutions() = null
+ override fun getEstimatedCaptureLatencyRange(size: Size?) = null
+ override fun getAvailableCaptureRequestKeys(): List<CaptureRequest.Key<out Any>> {
+ return supportedRequestKeys
+ }
+
+ override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> {
+ return mutableListOf()
+ }
+
+ override fun getSupportedPostviewResolutions(
+ captureSize: Size
+ ): MutableList<Pair<Int, Array<Size>>>? = null
+
+ override fun isCaptureProcessProgressAvailable() = false
+
+ override fun getRealtimeCaptureLatency(): Pair<Long, Long>? = null
+ override fun isPostviewAvailable() = false
+ override fun onInit(
+ cameraId: String,
+ cameraCharacteristics: CameraCharacteristics,
+ context: Context
+ ) {}
+
+ override fun onDeInit() {}
+ override fun onPresetSession(): CaptureStageImpl? = null
+
+ override fun onEnableSession(): CaptureStageImpl? = null
+
+ override fun onDisableSession(): CaptureStageImpl? = null
+ override fun onSessionType(): Int = SessionConfiguration.SESSION_REGULAR
+ }
+
+ private class FakeAdvancedVendorExtenderImpl(
+ val supportedRequestKeys: List<CaptureRequest.Key<out Any>>
+ ) : AdvancedExtenderImpl {
+ override fun isExtensionAvailable(
+ cameraId: String,
+ characteristicsMap: MutableMap<String, CameraCharacteristics>
+ ): Boolean = true
+
+ override fun init(
+ cameraId: String,
+ characteristicsMap: MutableMap<String, CameraCharacteristics>
+ ) {}
+ override fun getEstimatedCaptureLatencyRange(
+ cameraId: String,
+ captureOutputSize: Size?,
+ imageFormat: Int
+ ): Range<Long>? = null
+ override fun getSupportedPreviewOutputResolutions(
+ cameraId: String
+ ): Map<Int, MutableList<Size>> = emptyMap()
+ override fun getSupportedCaptureOutputResolutions(
+ cameraId: String
+ ): Map<Int, MutableList<Size>> = emptyMap()
+
+ override fun getSupportedPostviewResolutions(
+ captureSize: Size
+ ): Map<Int, MutableList<Size>> = emptyMap()
+ override fun getSupportedYuvAnalysisResolutions(cameraId: String) = null
+ override fun createSessionProcessor(): SessionProcessorImpl = DummySessionProcessorImpl()
+ override fun getAvailableCaptureRequestKeys():
+ List<CaptureRequest.Key<out Any>> = supportedRequestKeys
+
+ override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> = emptyList()
+ override fun isCaptureProcessProgressAvailable() = false
+ override fun isPostviewAvailable() = false
+ }
+
+ private class DummySessionProcessorImpl : SessionProcessorImpl {
+ override fun initSession(
+ cameraId: String,
+ cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+ context: Context,
+ surfaceConfigs: OutputSurfaceConfigurationImpl
+ ): Camera2SessionConfigImpl {
+ throw UnsupportedOperationException("Not supported")
+ }
+ override fun initSession(
+ cameraId: String,
+ cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+ context: Context,
+ previewSurfaceConfig: OutputSurfaceImpl,
+ imageCaptureSurfaceConfig: OutputSurfaceImpl,
+ imageAnalysisSurfaceConfig: OutputSurfaceImpl?
+ ): Camera2SessionConfigImpl {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun deInitSession() {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun startTrigger(
+ triggers: MutableMap<CaptureRequest.Key<*>, Any>,
+ callback: SessionProcessorImpl.CaptureCallback
+ ): Int {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun onCaptureSessionStart(requestProcessor: RequestProcessorImpl) {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun onCaptureSessionEnd() {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun stopRepeating() {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun startCapture(callback: SessionProcessorImpl.CaptureCallback): Int {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun startCaptureWithPostview(callback: SessionProcessorImpl.CaptureCallback): Int {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun abortCapture(captureSequenceId: Int) {
+ throw UnsupportedOperationException("Not supported")
+ }
+
+ override fun getRealtimeCaptureLatency(): Pair<Long, Long>? {
+ throw UnsupportedOperationException("Not supported")
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt
new file mode 100644
index 0000000..816aaf4
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.extensions.internal.compat.workaround
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.SessionConfiguration
+import android.os.Build
+import android.util.Pair
+import android.util.Size
+import androidx.camera.extensions.impl.CaptureStageImpl
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.util.ReflectionHelpers
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+ minSdk = Build.VERSION_CODES.LOLLIPOP,
+ instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class AvailableKeysRetrieverTest {
+ private val context: Context = RuntimeEnvironment.getApplication()
+ private val availableKeys = listOf<CaptureRequest.Key<out Any>>(
+ CaptureRequest.CONTROL_AE_REGIONS, CaptureRequest.CONTROL_AF_MODE
+ )
+ private val fakeImageCaptureExtenderImpl = FakeImageCaptureExtenderImpl(availableKeys)
+ private val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+
+ @Test
+ fun shouldInvokeOnInit() {
+ // 1. Arrange
+ ReflectionHelpers.setStaticField(Build::class.java, "BRAND", "SAMSUNG")
+ val retriever = AvailableKeysRetriever()
+
+ // 2. Act
+ val resultKeys = retriever.getAvailableCaptureRequestKeys(
+ fakeImageCaptureExtenderImpl, "0", characteristics, context)
+
+ // 3. Assert
+ assertThat(resultKeys).containsExactlyElementsIn(availableKeys)
+ assertThat(fakeImageCaptureExtenderImpl.invokeList).containsExactly(
+ "onInit", "getAvailableCaptureRequestKeys", "onDeInit"
+ )
+ }
+
+ @Test
+ fun shouldNotInvokeOnInit() {
+ // 1. Arrange
+ ReflectionHelpers.setStaticField(Build::class.java, "BRAND", "OTHER")
+ val retriever = AvailableKeysRetriever()
+
+ // 2. Act
+ val resultKeys = retriever.getAvailableCaptureRequestKeys(
+ fakeImageCaptureExtenderImpl, "0", characteristics, context)
+
+ // 3. Assert
+ assertThat(resultKeys).containsExactlyElementsIn(availableKeys)
+ assertThat(fakeImageCaptureExtenderImpl.invokeList).containsExactly(
+ "getAvailableCaptureRequestKeys"
+ )
+ }
+
+ class FakeImageCaptureExtenderImpl(
+ private var availableRequestKeys: List<CaptureRequest.Key<out Any>>
+ ) : ImageCaptureExtenderImpl {
+ val invokeList = mutableListOf<String>()
+ override fun isExtensionAvailable(
+ cameraId: String,
+ cameraCharacteristics: CameraCharacteristics
+ ): Boolean = true
+ override fun init(cameraId: String, cameraCharacteristics: CameraCharacteristics) {
+ invokeList.add("init")
+ }
+ override fun getCaptureProcessor() = null
+ override fun getCaptureStages(): List<CaptureStageImpl> = emptyList()
+ override fun getMaxCaptureStage() = 2
+ override fun getSupportedResolutions() = null
+ override fun getEstimatedCaptureLatencyRange(size: Size?) = null
+ override fun getAvailableCaptureRequestKeys(): List<CaptureRequest.Key<out Any>> {
+ invokeList.add("getAvailableCaptureRequestKeys")
+
+ return availableRequestKeys
+ }
+
+ override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> {
+ return mutableListOf()
+ }
+
+ override fun getSupportedPostviewResolutions(
+ captureSize: Size
+ ): MutableList<Pair<Int, Array<Size>>>? = null
+
+ override fun isCaptureProcessProgressAvailable() = false
+
+ override fun getRealtimeCaptureLatency(): Pair<Long, Long>? = null
+ override fun isPostviewAvailable() = false
+ override fun onInit(
+ cameraId: String,
+ cameraCharacteristics: CameraCharacteristics,
+ context: Context
+ ) {
+ invokeList.add("onInit")
+ }
+
+ override fun onDeInit() {
+ invokeList.add("onDeInit")
+ }
+ override fun onPresetSession(): CaptureStageImpl? = null
+ override fun onEnableSession(): CaptureStageImpl? = null
+ override fun onDisableSession(): CaptureStageImpl? = null
+ override fun onSessionType(): Int = SessionConfiguration.SESSION_REGULAR
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index c032e08..73c441f 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -23,6 +23,7 @@
import android.util.Size;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
@@ -80,6 +81,10 @@
private float mZoomRatio = -1;
private float mLinearZoom = -1;
private boolean mTorchEnabled = false;
+ private int mExposureCompensation = -1;
+
+ @Nullable
+ private FocusMeteringAction mLastSubmittedFocusMeteringAction = null;
public FakeCameraControl() {
this(NO_OP_CALLBACK);
@@ -189,9 +194,14 @@
@NonNull
@Override
public ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+ mExposureCompensation = exposure;
return Futures.immediateFuture(null);
}
+ public int getExposureCompensationIndex() {
+ return mExposureCompensation;
+ }
+
@NonNull
@Override
public ListenableFuture<List<Void>> submitStillCaptureRequests(
@@ -229,6 +239,7 @@
@Override
public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
@NonNull FocusMeteringAction action) {
+ mLastSubmittedFocusMeteringAction = action;
return Futures.immediateFuture(FocusMeteringResult.emptyInstance());
}
@@ -265,6 +276,11 @@
return mLinearZoom;
}
+ @Nullable
+ public FocusMeteringAction getLastSubmittedFocusMeteringAction() {
+ return mLastSubmittedFocusMeteringAction;
+ }
+
@Override
public void addInteropConfig(@NonNull Config config) {
for (Config.Option<?> option : config.listOptions()) {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
index 6fb7552..7a4b53a 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
@@ -92,6 +92,9 @@
private boolean mIsPrivateReprocessingSupported = false;
private float mIntrinsicZoomRatio = 1.0F;
+ private boolean mIsFocusMeteringSupported = false;
+
+ private ExposureState mExposureState = new FakeExposureState();
@NonNull
private final List<Quirk> mCameraQuirks = new ArrayList<>();
@@ -117,6 +120,37 @@
mZoomLiveData = new MutableLiveData<>(ImmutableZoomState.create(1.0f, 4.0f, 1.0f, 0.0f));
}
+ /**
+ * Sets the zoom parameter.
+ */
+ public void setZoom(float zoomRatio, float minZoomRatio, float maxZoomRatio, float linearZoom) {
+ mZoomLiveData.postValue(ImmutableZoomState.create(
+ zoomRatio, maxZoomRatio, minZoomRatio, linearZoom
+ ));
+ }
+
+ /**
+ * Sets the exposure compensation parameters.
+ */
+ public void setExposureState(int index, @NonNull Range<Integer> range,
+ @NonNull Rational step, boolean isSupported) {
+ mExposureState = new FakeExposureState(index, range, step, isSupported);
+ }
+
+ /**
+ * Sets the torch state.
+ */
+ public void setTorch(int torchState) {
+ mTorchState.postValue(torchState);
+ }
+
+ /**
+ * Sets the return value for {@link #isFocusMeteringSupported(FocusMeteringAction)}.
+ */
+ public void setIsFocusMeteringSupported(boolean supported) {
+ mIsFocusMeteringSupported = supported;
+ }
+
@Override
public int getLensFacing() {
return mLensFacing;
@@ -169,7 +203,7 @@
@NonNull
@Override
public ExposureState getExposureState() {
- return new FakeExposureState();
+ return mExposureState;
}
@NonNull
@@ -246,7 +280,7 @@
@Override
public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
- return false;
+ return mIsFocusMeteringSupported;
}
@Override
@@ -317,26 +351,41 @@
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
static final class FakeExposureState implements ExposureState {
+ private int mIndex = 0;
+ private Range<Integer> mRange = new Range<>(0, 0);
+ private Rational mStep = Rational.ZERO;
+ private boolean mIsSupported = true;
+
+ FakeExposureState() {
+ }
+ FakeExposureState(int index, Range<Integer> range,
+ Rational step, boolean isSupported) {
+ mIndex = index;
+ mRange = range;
+ mStep = step;
+ mIsSupported = isSupported;
+ }
+
@Override
public int getExposureCompensationIndex() {
- return 0;
+ return mIndex;
}
@NonNull
@Override
public Range<Integer> getExposureCompensationRange() {
- return Range.create(0, 0);
+ return mRange;
}
@NonNull
@Override
public Rational getExposureCompensationStep() {
- return Rational.ZERO;
+ return mStep;
}
@Override
public boolean isExposureCompensationSupported() {
- return true;
+ return mIsSupported;
}
}
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
index e08f64e..ee51c7d 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
@@ -33,6 +33,7 @@
import androidx.camera.core.impl.OptionsBundle
import androidx.camera.core.impl.OutputSurface
import androidx.camera.core.impl.RequestProcessor
+import androidx.camera.core.impl.RestrictedCameraControl
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.SessionProcessorSurface
@@ -46,8 +47,8 @@
@RequiresApi(28) // writing to PRIVATE surface requires API 28+
class FakeSessionProcessor(
- val inputFormatPreview: Int?,
- val inputFormatCapture: Int?
+ val inputFormatPreview: Int? = null,
+ val inputFormatCapture: Int? = null
) : SessionProcessor {
private lateinit var previewProcessorSurface: DeferrableSurface
private lateinit var captureProcessorSurface: DeferrableSurface
@@ -77,6 +78,9 @@
private var rotationDegrees = 0
private var jpegQuality = 100
+ @RestrictedCameraControl.CameraOperation
+ var restrictedCameraOperations: Set<Int> = emptySet()
+
fun releaseSurfaces() {
intermediaPreviewImageReader?.close()
intermediaCaptureImageReader?.close()
@@ -215,6 +219,11 @@
return latestParameters
}
+ @RestrictedCameraControl.CameraOperation
+ override fun getSupportedCameraOperations(): Set<Int> {
+ return restrictedCameraOperations
+ }
+
override fun startRepeating(callback: SessionProcessor.CaptureCallback): Int {
startRepeatingCalled.complete(SystemClock.elapsedRealtimeNanos())
val builder = RequestProcessorRequest.Builder().apply {