Aperture: Use CameraX APIs for logical cameras
Took 'em long enough
Change-Id: I52c11adb8e296a74e19e20856b12f22abefb2633
diff --git a/app/src/main/java/org/lineageos/aperture/camera/BaseCamera.kt b/app/src/main/java/org/lineageos/aperture/camera/BaseCamera.kt
new file mode 100644
index 0000000..7f52c32
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/camera/BaseCamera.kt
@@ -0,0 +1,60 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.camera
+
+import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import org.lineageos.aperture.models.CameraFacing
+import org.lineageos.aperture.models.CameraType
+import org.lineageos.aperture.viewmodels.CameraViewModel
+import kotlin.reflect.safeCast
+
+/**
+ * A generic camera device.
+ * The only contract in place is that the camera ID must be unique also between different
+ * implementations (guaranteed by Android).
+ */
+@androidx.camera.camera2.interop.ExperimentalCamera2Interop
+@androidx.camera.core.ExperimentalLensFacing
+abstract class BaseCamera(cameraInfo: CameraInfo, model: CameraViewModel) {
+ /**
+ * The [CameraSelector] for this camera.
+ */
+ abstract val cameraSelector: CameraSelector
+
+ /**
+ * The [Camera2CameraInfo] of this camera.
+ */
+ protected val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
+
+ /**
+ * Camera2's camera ID.
+ */
+ val cameraId = camera2CameraInfo.cameraId
+
+ /**
+ * The [CameraFacing] of this camera.
+ */
+ val cameraFacing = when (cameraInfo.lensFacing) {
+ CameraSelector.LENS_FACING_UNKNOWN -> CameraFacing.UNKNOWN
+ CameraSelector.LENS_FACING_FRONT -> CameraFacing.FRONT
+ CameraSelector.LENS_FACING_BACK -> CameraFacing.BACK
+ CameraSelector.LENS_FACING_EXTERNAL -> CameraFacing.EXTERNAL
+ else -> throw Exception("Unknown lens facing value")
+ }
+
+ /**
+ * The [CameraType] of this camera.
+ */
+ val cameraType = cameraFacing.cameraType
+
+ override fun equals(other: Any?) = this::class.safeCast(other)?.let {
+ this.cameraId == it.cameraId
+ } ?: false
+
+ override fun hashCode() = this::class.qualifiedName.hashCode() + cameraId.hashCode()
+}
diff --git a/app/src/main/java/org/lineageos/aperture/camera/Camera.kt b/app/src/main/java/org/lineageos/aperture/camera/Camera.kt
index 2c820b0..556575b 100644
--- a/app/src/main/java/org/lineageos/aperture/camera/Camera.kt
+++ b/app/src/main/java/org/lineageos/aperture/camera/Camera.kt
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project
+ * SPDX-FileCopyrightText: 2022-2024 The LineageOS Project
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,9 +8,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraMetadata
import android.os.Build
-import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.CameraInfo
-import androidx.camera.core.CameraSelector
import androidx.camera.video.Recorder
import org.lineageos.aperture.ext.*
import org.lineageos.aperture.models.CameraFacing
@@ -27,7 +25,6 @@
import org.lineageos.aperture.models.VideoQualityInfo
import org.lineageos.aperture.models.VideoStabilizationMode
import org.lineageos.aperture.viewmodels.CameraViewModel
-import kotlin.reflect.safeCast
/**
* Class representing a device camera
@@ -35,26 +32,16 @@
@androidx.camera.camera2.interop.ExperimentalCamera2Interop
@androidx.camera.core.ExperimentalLensFacing
@androidx.camera.core.ExperimentalZeroShutterLag
-class Camera(cameraInfo: CameraInfo, model: CameraViewModel) {
- val cameraSelector = cameraInfo.cameraSelector
-
- private val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
- val cameraId = camera2CameraInfo.cameraId
-
- val cameraFacing = when (cameraInfo.lensFacing) {
- CameraSelector.LENS_FACING_UNKNOWN -> CameraFacing.UNKNOWN
- CameraSelector.LENS_FACING_FRONT -> CameraFacing.FRONT
- CameraSelector.LENS_FACING_BACK -> CameraFacing.BACK
- CameraSelector.LENS_FACING_EXTERNAL -> CameraFacing.EXTERNAL
- else -> throw Exception("Unknown lens facing value")
- }
-
- val cameraType = cameraFacing.cameraType
+class Camera(cameraInfo: CameraInfo, model: CameraViewModel) : BaseCamera(cameraInfo, model) {
+ override val cameraSelector = cameraInfo.cameraSelector
val exposureCompensationRange = cameraInfo.exposureState.exposureCompensationRange
private val hasFlashUnit = cameraInfo.hasFlashUnit()
- val isLogical = camera2CameraInfo.physicalCameraIds.size > 1
+ private val physicalCameras = cameraInfo.physicalCameraInfos.map {
+ PhysicalCamera(it, model, this)
+ }
+ val isLogical = physicalCameras.size > 1
val intrinsicZoomRatio = cameraInfo.intrinsicZoomRatio
val logicalZoomRatios = model.getLogicalZoomRatios(cameraId)
@@ -254,15 +241,6 @@
}
}
- override fun equals(other: Any?): Boolean {
- val camera = this::class.safeCast(other) ?: return false
- return this.cameraId == camera.cameraId
- }
-
- override fun hashCode(): Int {
- return this::class.qualifiedName.hashCode() + cameraId.hashCode()
- }
-
fun supportsExtensionMode(extensionMode: Int): Boolean {
return supportedExtensionModes.contains(extensionMode)
}
diff --git a/app/src/main/java/org/lineageos/aperture/camera/PhysicalCamera.kt b/app/src/main/java/org/lineageos/aperture/camera/PhysicalCamera.kt
new file mode 100644
index 0000000..91e35d8
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/camera/PhysicalCamera.kt
@@ -0,0 +1,25 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.camera
+
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import org.lineageos.aperture.viewmodels.CameraViewModel
+
+/**
+ * A logical camera's backing physical camera.
+ */
+@androidx.camera.camera2.interop.ExperimentalCamera2Interop
+@androidx.camera.core.ExperimentalLensFacing
+class PhysicalCamera(
+ cameraInfo: CameraInfo,
+ model: CameraViewModel,
+ logicalCamera: Camera,
+) : BaseCamera(cameraInfo, model) {
+ override val cameraSelector = CameraSelector.Builder()
+ .setPhysicalCameraId(cameraId)
+ .build()
+}
diff --git a/app/src/main/java/org/lineageos/aperture/ext/Camera2CameraInfo.kt b/app/src/main/java/org/lineageos/aperture/ext/Camera2CameraInfo.kt
deleted file mode 100644
index a08eff7..0000000
--- a/app/src/main/java/org/lineageos/aperture/ext/Camera2CameraInfo.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 The LineageOS Project
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package org.lineageos.aperture.ext
-
-import android.hardware.camera2.CameraCharacteristics
-import android.os.Build
-import androidx.camera.camera2.interop.Camera2CameraInfo
-
-/**
- * We're adding this here since it's private. We're supposed to use
- * CameraCharacteristics.getPhysicalCameraIds() but it's not exposed by CameraX yet.
- */
-private val LOGICAL_MULTI_CAMERA_PHYSICAL_IDS by lazy {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- CameraCharacteristics.Key(
- "android.logicalMultiCamera.physicalIds",
- ByteArray::class.java
- )
- } else {
- throw Exception("Requesting LOGICAL_MULTI_CAMERA_PHYSICAL_IDS on older Android version")
- }
-}
-
-/**
- * Return the set of physical camera ids that this logical {@link CameraDevice} is made
- * up of.
- *
- * If the camera device isn't a logical camera, return an empty set.
- */
-val Camera2CameraInfo.physicalCameraIds: Set<String>
- @androidx.camera.camera2.interop.ExperimentalCamera2Interop
- get() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- return setOf()
- }
-
- val availableCapabilities = getCameraCharacteristic(
- CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
- ) ?: throw AssertionError(
- "android.request.availableCapabilities must be non-null in the characteristics"
- )
- if (!availableCapabilities.contains(
- CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
- )
- ) {
- return setOf()
- }
-
- val physicalCamIds: ByteArray = getCameraCharacteristic(
- LOGICAL_MULTI_CAMERA_PHYSICAL_IDS
- ) ?: throw AssertionError(
- "android.logicalMultiCamera.physicalIds must be non-null in the characteristics"
- )
-
- val physicalCamIdString = String(physicalCamIds, Charsets.UTF_8)
- val physicalCameraIdArray = physicalCamIdString.split(0.toChar())
-
- return physicalCameraIdArray.toSet()
- }