Merge "CameraController: catch invalid UseCase combination error in LifecycleCameraController" into androidx-main
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
index 630e7f3..fcd7805 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt
@@ -112,6 +112,35 @@
         }
     }
 
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidEffectsCombination_throwsException() {
+        // Arrange: setup PreviewView and CameraController
+        var previewView: PreviewView? = null
+        activityScenario!!.onActivity {
+            // Arrange.
+            previewView = PreviewView(context)
+            it.setContentView(previewView)
+            previewView!!.controller = controller
+            controller!!.bindToLifecycle(FakeLifecycleOwner())
+            controller!!.initializationFuture.get()
+        }
+        waitUtilPreviewViewIsReady(previewView!!)
+
+        // Act: set the same effect twice, which is invalid.
+        val previewEffect = FakePreviewEffect(
+            mainThreadExecutor(),
+            FakeSurfaceProcessor(mainThreadExecutor())
+        )
+        instrumentation.runOnMainSync {
+            controller!!.setEffects(
+                listOf(
+                    previewEffect,
+                    previewEffect
+                )
+            )
+        }
+    }
+
     @Test
     fun setEffectBundle_effectSetOnUseCase() {
         // Arrange: setup PreviewView and CameraController
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index ce1a726..6a9ec13 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -420,6 +420,9 @@
 
     /**
      * Implemented by children to refresh after {@link UseCase} is changed.
+     *
+     * @throws IllegalStateException for invalid {@link UseCase} combinations.
+     * @throws RuntimeException      for invalid {@link CameraEffect} combinations.
      */
     @Nullable
     abstract Camera startCamera();
@@ -1928,21 +1931,19 @@
     /**
      * @param restoreStateRunnable runnable to restore the controller to the previous good state if
      *                             the binding fails.
-     * @throws IllegalStateException if binding fails.
+     * @throws IllegalStateException for invalid {@link UseCase} combinations.
+     * @throws RuntimeException      for invalid {@link CameraEffect} combinations.
      */
     void startCameraAndTrackStates(@Nullable Runnable restoreStateRunnable) {
         try {
             mCamera = startCamera();
-        } catch (IllegalArgumentException exception) {
+        } catch (RuntimeException exception) {
+            // Restore the previous state before re-throwing the exception.
             if (restoreStateRunnable != null) {
                 restoreStateRunnable.run();
             }
-            // Catches the core exception and throw a more readable one.
-            String errorMessage =
-                    "The selected camera does not support the enabled use cases. Please "
-                            + "disable use case and/or select a different camera. e.g. "
-                            + "#setVideoCaptureEnabled(false)";
-            throw new IllegalStateException(errorMessage, exception);
+            // This exception will handled by the app.
+            throw exception;
         }
         if (!isCameraAttached()) {
             Logger.d(TAG, CAMERA_NOT_ATTACHED);
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
index 2f751af..9f695f9 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/LifecycleCameraController.java
@@ -30,6 +30,7 @@
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.Camera;
+import androidx.camera.core.CameraEffect;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.core.impl.utils.Threads;
@@ -109,6 +110,8 @@
      * Unbind and rebind all use cases to {@link LifecycleOwner}.
      *
      * @return null if failed to start camera.
+     * @throws IllegalStateException for invalid {@link UseCase} combinations.
+     * @throws RuntimeException      for invalid {@link CameraEffect} combinations.
      */
     @RequiresPermission(Manifest.permission.CAMERA)
     @Override
@@ -128,7 +131,16 @@
             // Use cases can't be created.
             return null;
         }
-        return mCameraProvider.bindToLifecycle(mLifecycleOwner, mCameraSelector, useCaseGroup);
+        try {
+            return mCameraProvider.bindToLifecycle(mLifecycleOwner, mCameraSelector, useCaseGroup);
+        } catch (IllegalArgumentException e) {
+            // Catches the invalid use case combination exception and throw a more readable one.
+            String errorMessage =
+                    "The selected camera does not support the enabled use cases. Please "
+                            + "disable use case and/or select a different camera. e.g. "
+                            + "#setVideoCaptureEnabled(false)";
+            throw new IllegalStateException(errorMessage, e);
+        }
     }
 
     /**