Aperture: Add support for logical cameras

Change-Id: Ied678db241a38bdb8ae0910edd25da5a4bc1aa20
diff --git a/app/src/main/java/org/lineageos/aperture/Camera2CameraInfoExt.kt b/app/src/main/java/org/lineageos/aperture/Camera2CameraInfoExt.kt
new file mode 100644
index 0000000..7f640ce
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/Camera2CameraInfoExt.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The LineageOS Project
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture
+
+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()
+    }
diff --git a/app/src/main/java/org/lineageos/aperture/utils/Camera.kt b/app/src/main/java/org/lineageos/aperture/utils/Camera.kt
index d78e1d0..4e44874 100644
--- a/app/src/main/java/org/lineageos/aperture/utils/Camera.kt
+++ b/app/src/main/java/org/lineageos/aperture/utils/Camera.kt
@@ -12,6 +12,7 @@
 import androidx.camera.video.Quality
 import androidx.camera.video.QualitySelector
 import org.lineageos.aperture.getSupportedModes
+import org.lineageos.aperture.physicalCameraIds
 import kotlin.reflect.safeCast
 
 /**
@@ -35,6 +36,9 @@
     val exposureCompensationRange = cameraInfo.exposureState.exposureCompensationRange
     val hasFlashUnit = cameraInfo.hasFlashUnit()
 
+    val physicalCameraIds = camera2CameraInfo.physicalCameraIds
+    val isLogical = physicalCameraIds.isNotEmpty()
+
     val supportedVideoQualities: MutableList<Quality> =
         QualitySelector.getSupportedQualities(cameraInfo)