Aperture: Don't start app if no cameras are found on the device

Change-Id: Ia9bce63c36592fc3d602749f1e27ab941ebe0e93
diff --git a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
index 5f16937..a08b766 100644
--- a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
@@ -614,7 +614,12 @@
         }
 
         // Select a camera
-        camera = cameraManager.getCameraOfFacingOrFirstAvailable(initialCameraFacing, cameraMode)
+        camera = cameraManager.getCameraOfFacingOrFirstAvailable(
+            initialCameraFacing, cameraMode
+        ) ?: run {
+            noCamera()
+            return
+        }
 
         // Setup window insets
         ViewCompat.setOnApplyWindowInsetsListener(mainLayout) { _, windowInsets ->
@@ -1507,6 +1512,9 @@
             )
 
             else -> camera
+        } ?: run {
+            noCamera()
+            return
         }
 
         // If the current camera doesn't support the selected camera mode
@@ -1514,7 +1522,10 @@
         if (!camera.supportsCameraMode(cameraMode)) {
             camera = cameraManager.getCameraOfFacingOrFirstAvailable(
                 camera.cameraFacing, cameraMode
-            )
+            ) ?: run {
+                noCamera()
+                return
+            }
         }
 
         // Fallback to ExtensionMode.NONE if necessary
@@ -1882,7 +1893,11 @@
 
         (flipCameraButton.drawable as AnimatedVectorDrawable).start()
 
-        camera = cameraManager.getNextCamera(camera, cameraMode)
+        camera = cameraManager.getNextCamera(camera, cameraMode) ?: run {
+            noCamera()
+            return
+        }
+
         sharedPreferences.lastCameraFacing = camera.cameraFacing
 
         bindCameraUseCases()
@@ -2448,6 +2463,16 @@
     }
 
     /**
+     * Show a toast warning the user that no camera is available and close the activity.
+     */
+    private fun noCamera() {
+        Toast.makeText(
+            this, R.string.error_no_cameras_available, Toast.LENGTH_LONG
+        ).show()
+        finish()
+    }
+
+    /**
      * Zoom out by a power of 2.
      */
     private fun zoomOut() {
diff --git a/app/src/main/java/org/lineageos/aperture/camera/CameraManager.kt b/app/src/main/java/org/lineageos/aperture/camera/CameraManager.kt
index f394d28..f07db70 100644
--- a/app/src/main/java/org/lineageos/aperture/camera/CameraManager.kt
+++ b/app/src/main/java/org/lineageos/aperture/camera/CameraManager.kt
@@ -179,9 +179,15 @@
         }
     }
 
+    /**
+     * Get a suitable [Camera] for the provided [CameraFacing] and [CameraMode].
+     * @param cameraFacing The requested [CameraFacing]
+     * @param cameraMode The requested [CameraMode]
+     * @return A [Camera] that is compatible with the provided configuration or null
+     */
     fun getCameraOfFacingOrFirstAvailable(
         cameraFacing: CameraFacing, cameraMode: CameraMode
-    ): Camera {
+    ): Camera? {
         val camera = when (cameraFacing) {
             CameraFacing.BACK -> mainBackCamera
             CameraFacing.FRONT -> mainFrontCamera
@@ -190,17 +196,23 @@
         }
         return camera?.let {
             if (cameraMode == CameraMode.VIDEO && !it.supportsVideoRecording) {
-                availableCamerasSupportingVideoRecording.first()
+                availableCamerasSupportingVideoRecording.firstOrNull()
             } else {
                 it
             }
         } ?: when (cameraMode) {
-            CameraMode.VIDEO -> availableCamerasSupportingVideoRecording.first()
-            else -> availableCameras.first()
+            CameraMode.VIDEO -> availableCamerasSupportingVideoRecording.firstOrNull()
+            else -> availableCameras.firstOrNull()
         }
     }
 
-    fun getNextCamera(camera: Camera, cameraMode: CameraMode): Camera {
+    /**
+     * Return the next camera, used for flip camera.
+     * @param camera The current [Camera] used
+     * @param cameraMode The current [CameraMode]
+     * @return The next camera, may return null if all the cameras disappeared
+     */
+    fun getNextCamera(camera: Camera, cameraMode: CameraMode): Camera? {
         val cameras = when (cameraMode) {
             CameraMode.VIDEO -> availableCamerasSupportingVideoRecording
             else -> availableCameras
@@ -218,7 +230,7 @@
         ) + 1
 
         return if (newCameraIndex >= cameras.size) {
-            cameras.first()
+            cameras.firstOrNull()
         } else {
             cameras[newCameraIndex]
         }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ca98ce5..a5ba743 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -165,6 +165,7 @@
     <string name="error_do_not_disturb_mode_enabled">Do Not Disturb mode is enabled. Disable it and reopen the app.</string>
     <string name="error_unknown_recoverable">A recoverable unknown error has been found. Please report this to the developers.</string>
     <string name="error_unknown_critical">A critical unknown error has been found. Please report this to the developers.</string>
+    <string name="error_no_cameras_available">No cameras were found on the device, cannot start the app.</string>
 
     <!-- Gesture actions -->
     <string name="gesture_action_shutter">Shutter</string>