Aperture: Rework framerate handling

* Assume a specific framerate will always be available, per AOSP docs:
  For devices at the LEGACY level or above: (...) a camera device must
  either not support any CamcorderProfile, or support at least one
  normal CamcorderProfile that has videoFrameRate x >= 24.
* Always set a framerate, the default is 30fps and the app will try to
  find a lower or higher framerate
* If the camera has less than two framerates available, disable the
  framerate button
* In case the camera supports no known framerate, let CameraX handle it

Change-Id: I1826328d0399469a95b03f0676a7a960fc0a0317
diff --git a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
index 3a2a4d9..4292d97 100644
--- a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
@@ -175,9 +175,9 @@
         get() = camera.supportedVideoQualities.keys.toList()
     private val supportedVideoFramerates: List<Framerate>
         get() = camera.supportedVideoQualities.getOrDefault(
-            sharedPreferences.videoQuality, listOf(Framerate.FPS_AUTO)
+            sharedPreferences.videoQuality, listOf()
         )
-    private var videoFramerate = Framerate.FPS_AUTO
+    private var videoFramerate: Framerate? = null
     private lateinit var audioConfig: AudioConfig
     private var recording: Recording? = null
 
@@ -888,10 +888,10 @@
                 }
                 cameraController.videoCaptureTargetQuality = sharedPreferences.videoQuality
 
-                // Fallback to Framerate.FPS_AUTO if necessary
-                if (!supportedVideoFramerates.contains(videoFramerate)) {
-                    videoFramerate = Framerate.FPS_AUTO
-                }
+                // Set proper video framerate
+                videoFramerate = (Framerate::getLowerOrHigher)(
+                    videoFramerate ?: Framerate.FPS_30, supportedVideoFramerates
+                )
 
                 CameraController.VIDEO_CAPTURE
             }
@@ -945,7 +945,7 @@
                             if (cameraMode == CameraMode.VIDEO) {
                                 videoFramerate
                             } else {
-                                Framerate.FPS_AUTO
+                                null
                             }
                         )
                         setStabilizationMode(
@@ -1148,12 +1148,12 @@
     }
 
     private fun updateVideoFramerateIcon() {
+        videoFramerateButton.isEnabled = supportedVideoFramerates.size > 1
         videoFramerateButton.isVisible = cameraMode == CameraMode.VIDEO
 
-        videoFramerateButton.text = when (videoFramerate) {
-            Framerate.FPS_AUTO -> resources.getString(R.string.video_framerate_auto)
-            else -> resources.getString(R.string.video_framerate_value, videoFramerate.value)
-        }
+        videoFramerateButton.text = videoFramerate?.let {
+            resources.getString(R.string.video_framerate_value, it.value)
+        } ?: resources.getString(R.string.video_framerate_auto)
     }
 
     private fun cycleVideoFramerate() {
diff --git a/app/src/main/java/org/lineageos/aperture/CaptureRequestOptionsBuilderExt.kt b/app/src/main/java/org/lineageos/aperture/CaptureRequestOptionsBuilderExt.kt
index 9bc8d21..68faad5 100644
--- a/app/src/main/java/org/lineageos/aperture/CaptureRequestOptionsBuilderExt.kt
+++ b/app/src/main/java/org/lineageos/aperture/CaptureRequestOptionsBuilderExt.kt
@@ -13,10 +13,10 @@
 import org.lineageos.aperture.utils.StabilizationMode
 
 @androidx.camera.camera2.interop.ExperimentalCamera2Interop
-fun CaptureRequestOptions.Builder.setFramerate(framerate: Framerate) {
-    framerate.range?.let {
+fun CaptureRequestOptions.Builder.setFramerate(framerate: Framerate?) {
+    framerate?.let {
         setCaptureRequestOption(
-            CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, it
+            CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, it.range
         )
     } ?: run {
         clearCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE)
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 1a3093d..1b5fa70 100644
--- a/app/src/main/java/org/lineageos/aperture/utils/Camera.kt
+++ b/app/src/main/java/org/lineageos/aperture/utils/Camera.kt
@@ -75,15 +75,12 @@
 
     var intrinsicZoomRatio = 1f
 
-    private val supportedVideoFramerates = mutableListOf(Framerate.FPS_AUTO).apply {
+    private val supportedVideoFramerates =
         camera2CameraInfo.getCameraCharacteristic(
             CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
         )?.mapNotNull { range ->
             Framerate.fromRange(range)
-        }?.let {
-            addAll(it)
-        }
-    }.distinct().sorted().toList()
+        }?.distinct()?.sorted() ?: listOf()
     val supportedVideoQualities = QualitySelector.getSupportedQualities(cameraInfo).associateWith {
         supportedVideoFramerates + cameraManager.getAdditionalVideoFramerates(cameraId, it)
     }.toSortedMap { a, b ->
diff --git a/app/src/main/java/org/lineageos/aperture/utils/Framerate.kt b/app/src/main/java/org/lineageos/aperture/utils/Framerate.kt
index edea0d9..598771c 100644
--- a/app/src/main/java/org/lineageos/aperture/utils/Framerate.kt
+++ b/app/src/main/java/org/lineageos/aperture/utils/Framerate.kt
@@ -8,13 +8,23 @@
 import android.util.Range
 
 enum class Framerate(val value: Int) {
-    FPS_AUTO(-1),
     FPS_24(24),
     FPS_30(30),
     FPS_60(60),
     FPS_120(120);
 
-    val range = if (value == -1) null else Range(value, value)
+    val range = Range(value, value)
+
+    /**
+     * Get the closer framerate to the requested one, first finding a lower one
+     * then checking for a higher one if no one exists.
+     */
+    fun getLowerOrHigher(framerates: List<Framerate>): Framerate? {
+        val smaller = framerates.filter { it <= this }.sortedDescending()
+        val bigger = framerates.filter { it > this }.sorted()
+
+        return (smaller + bigger).firstOrNull()
+    }
 
     companion object {
         fun fromValue(value: Int) = values().firstOrNull { it.value == value }