From d25d121c2cde837c9fdb5e402a68f3827cb1e789 Mon Sep 17 00:00:00 2001 From: David Jia Date: Thu, 12 Mar 2026 14:10:30 -0700 Subject: [PATCH 1/8] init --- .../jetpackcamera/core/camera/CameraSystem.kt | 2 + .../core/camera/CameraXCameraSystem.kt | 47 +++++++++++++++++++ .../core/camera/test/FakeCameraSystem.kt | 4 ++ 3 files changed, 53 insertions(+) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt index b6821300e..0cb2c3dd6 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt @@ -131,6 +131,8 @@ interface CameraSystem { suspend fun setCaptureMode(captureMode: CaptureMode) + suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List + /** * Represents the events required for screen flash. */ diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt index cd178595f..efbe0ba07 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt @@ -36,7 +36,9 @@ import androidx.camera.core.takePicture import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance +import androidx.camera.video.Quality import androidx.camera.video.Recorder +import androidx.concurrent.futures.await import androidx.core.net.toFile import com.google.jetpackcamera.core.camera.CameraCoreUtil.getAllCamerasPropertiesJSONArray import com.google.jetpackcamera.core.camera.CameraCoreUtil.writeFileExternalStorage @@ -1034,6 +1036,51 @@ constructor( } } + override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { + val localCameraProvider = if (::cameraProvider.isInitialized) { + cameraProvider + } else { + try { + ProcessCameraProvider.getInstance(application).await() + } catch (e: Exception) { + return emptyList() + } + } + val availableCameras = localCameraProvider.availableCameraInfos + if (availableCameras.isEmpty()) return emptyList() + + val result = mutableListOf() + val selector = lensFacing.toCameraSelector() + selector.filter(availableCameras).firstOrNull()?.let { camInfo -> + val imageCapabilities = ImageCapture.getImageCaptureCapabilities(camInfo) + val supportedImageFormats = imageCapabilities.supportedOutputFormats + val videoCapabilities = Recorder.getVideoCapabilities(camInfo) + + for (supportedImageFormat in supportedImageFormats) { + when (supportedImageFormat) { + ImageCapture.OUTPUT_FORMAT_JPEG -> { result.add("image/jpeg")} + ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR -> { + result.add("image/jpeg-ultrahdr") + } + } + } + if (videoCapabilities.getSupportedQualities( + DynamicRange.SDR.toCXDynamicRange() + ).isNotEmpty() + ) { + // If it supports any SDR quality (SD, HD, FHD, etc.), it can record standard AVC/MP4 + result.add("video/mp4") + result.add("video/avc") + // If it supports HLG 10-bit HDR, CameraX will use HEVC (H.265) under the hood + if (videoCapabilities.supportedDynamicRanges.contains( + DynamicRange.HLG10.toCXDynamicRange())) { + result.add("video/hevc") + } + } + } + return result + } + private suspend fun handleLowLightBoostErrors() { currentCameraState.map { it.lowLightBoostState }.distinctUntilChanged().collect { state -> if (state is LowLightBoostState.Error) { diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt index 842afcf11..bbfff316a 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt @@ -266,4 +266,8 @@ class FakeCameraSystem(defaultCameraSettings: CameraAppSettings = CameraAppSetti old.copy(captureMode = captureMode) } } + + override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { + return listOf("image/jpeg", "video/mp4") + } } From 50795d45ad96b0b2f76499fd12ed8e95bcf48acc Mon Sep 17 00:00:00 2001 From: David Jia Date: Thu, 12 Mar 2026 14:19:46 -0700 Subject: [PATCH 2/8] kdocs & spotless --- .../jetpackcamera/core/camera/CameraSystem.kt | 8 ++++++++ .../core/camera/CameraXCameraSystem.kt | 20 +++++++++++++------ .../core/camera/test/FakeCameraSystem.kt | 3 +++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt index 0cb2c3dd6..4c2aca7bb 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt @@ -131,6 +131,14 @@ interface CameraSystem { suspend fun setCaptureMode(captureMode: CaptureMode) + /** + * Returns a list of supported MIME types for the given [lensFacing]. + * + * This function queries the underlying camera hardware for its capabilities. + * + * @param lensFacing The camera lens to query. + * @return A list of supported MIME type strings (e.g., "image/jpeg", "video/mp4"). + */ suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List /** diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt index efbe0ba07..3937d5d3d 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt @@ -36,7 +36,6 @@ import androidx.camera.core.takePicture import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance -import androidx.camera.video.Quality import androidx.camera.video.Recorder import androidx.concurrent.futures.await import androidx.core.net.toFile @@ -1036,6 +1035,15 @@ constructor( } } + /** + * Returns a list of supported MIME types for the given [lensFacing]. + * + * This function can be called before the camera is initialized. It will temporarily create a + * [ProcessCameraProvider] instance if one is not already available. + * + * @param lensFacing The camera lens to query for supported types. + * @return A list of MIME type strings. Returns an empty list if capabilities cannot be queried. + */ override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { val localCameraProvider = if (::cameraProvider.isInitialized) { cameraProvider @@ -1058,10 +1066,8 @@ constructor( for (supportedImageFormat in supportedImageFormats) { when (supportedImageFormat) { - ImageCapture.OUTPUT_FORMAT_JPEG -> { result.add("image/jpeg")} - ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR -> { - result.add("image/jpeg-ultrahdr") - } + ImageCapture.OUTPUT_FORMAT_JPEG -> result.add("image/jpeg") + ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR -> result.add("image/jpeg-ultrahdr") } } if (videoCapabilities.getSupportedQualities( @@ -1073,7 +1079,9 @@ constructor( result.add("video/avc") // If it supports HLG 10-bit HDR, CameraX will use HEVC (H.265) under the hood if (videoCapabilities.supportedDynamicRanges.contains( - DynamicRange.HLG10.toCXDynamicRange())) { + DynamicRange.HLG10.toCXDynamicRange() + ) + ) { result.add("video/hevc") } } diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt index bbfff316a..a72cd2d12 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt @@ -267,6 +267,9 @@ class FakeCameraSystem(defaultCameraSettings: CameraAppSettings = CameraAppSetti } } + /** + * Returns a fake list of supported MIME types. + */ override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { return listOf("image/jpeg", "video/mp4") } From 01a511ad19fc1d378468ae342f0810df5be0e524 Mon Sep 17 00:00:00 2001 From: David Jia Date: Mon, 16 Mar 2026 14:19:52 -0700 Subject: [PATCH 3/8] post init call --- .../jetpackcamera/core/camera/CameraSystem.kt | 9 +-- .../core/camera/CameraXCameraSystem.kt | 63 +++++++------------ .../core/camera/test/FakeCameraSystem.kt | 4 +- 3 files changed, 26 insertions(+), 50 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt index 4c2aca7bb..58ec5f449 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt @@ -132,14 +132,11 @@ interface CameraSystem { suspend fun setCaptureMode(captureMode: CaptureMode) /** - * Returns a list of supported MIME types for the given [lensFacing]. + * Returns a set of MIME types supported by the underlying camera hardware capabilities and JCA. * - * This function queries the underlying camera hardware for its capabilities. - * - * @param lensFacing The camera lens to query. - * @return A list of supported MIME type strings (e.g., "image/jpeg", "video/mp4"). + * @return A [Set] of strings representing the supported MIME types (e.g. image/jpeg, ""video/mp4). */ - suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List + suspend fun getSupportedMimeTypes(): Set /** * Represents the events required for screen flash. diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt index 3937d5d3d..93264e6b5 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt @@ -37,7 +37,6 @@ import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.camera.video.Recorder -import androidx.concurrent.futures.await import androidx.core.net.toFile import com.google.jetpackcamera.core.camera.CameraCoreUtil.getAllCamerasPropertiesJSONArray import com.google.jetpackcamera.core.camera.CameraCoreUtil.writeFileExternalStorage @@ -1036,55 +1035,35 @@ constructor( } /** - * Returns a list of supported MIME types for the given [lensFacing]. + * Returns a set of MIME types supported by the underlying camera hardware capabilities and JCA. * - * This function can be called before the camera is initialized. It will temporarily create a - * [ProcessCameraProvider] instance if one is not already available. + * Empty set is returned if there are is available [CameraInfo]. + * The [CameraConstraints] of the default lens is used to determine the supported MIME types. + * image/jpeg is supported if the device supports JPEG format for the default stream. + * video/mp4 is supported if any dynamic ranges are supported. * - * @param lensFacing The camera lens to query for supported types. - * @return A list of MIME type strings. Returns an empty list if capabilities cannot be queried. + * @return A [Set] of strings representing the supported MIME types. */ - override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { - val localCameraProvider = if (::cameraProvider.isInitialized) { - cameraProvider - } else { - try { - ProcessCameraProvider.getInstance(application).await() - } catch (e: Exception) { - return emptyList() - } - } - val availableCameras = localCameraProvider.availableCameraInfos - if (availableCameras.isEmpty()) return emptyList() - - val result = mutableListOf() - val selector = lensFacing.toCameraSelector() - selector.filter(availableCameras).firstOrNull()?.let { camInfo -> - val imageCapabilities = ImageCapture.getImageCaptureCapabilities(camInfo) - val supportedImageFormats = imageCapabilities.supportedOutputFormats - val videoCapabilities = Recorder.getVideoCapabilities(camInfo) - - for (supportedImageFormat in supportedImageFormats) { - when (supportedImageFormat) { - ImageCapture.OUTPUT_FORMAT_JPEG -> result.add("image/jpeg") - ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR -> result.add("image/jpeg-ultrahdr") - } + override suspend fun getSupportedMimeTypes(): Set { + val result = mutableSetOf() + currentSettings.value?.let { cameraAppSettings -> + val lensFacing = cameraAppSettings.cameraLensFacing + val cameraConstraints = systemConstraints.perLensConstraints[lensFacing] + if (cameraConstraints == null) { + return result } - if (videoCapabilities.getSupportedQualities( - DynamicRange.SDR.toCXDynamicRange() - ).isNotEmpty() - ) { - // If it supports any SDR quality (SD, HD, FHD, etc.), it can record standard AVC/MP4 - result.add("video/mp4") - result.add("video/avc") - // If it supports HLG 10-bit HDR, CameraX will use HEVC (H.265) under the hood - if (videoCapabilities.supportedDynamicRanges.contains( - DynamicRange.HLG10.toCXDynamicRange() + val streamConfig = cameraAppSettings.streamConfig + cameraConstraints.supportedImageFormatsMap[streamConfig]?.let { + if (it.contains( + ImageOutputFormat.JPEG ) ) { - result.add("video/hevc") + result.add("image/jpeg") } } + if (cameraConstraints.supportedDynamicRanges.isNotEmpty()) { + result.add("video/mp4") + } } return result } diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt index a72cd2d12..f92becb3f 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt @@ -270,7 +270,7 @@ class FakeCameraSystem(defaultCameraSettings: CameraAppSettings = CameraAppSetti /** * Returns a fake list of supported MIME types. */ - override suspend fun getSupportedMimeTypes(lensFacing: LensFacing): List { - return listOf("image/jpeg", "video/mp4") + override suspend fun getSupportedMimeTypes(): Set { + return setOf("image/jpeg", "video/mp4") } } From d0de05057f029d9dba56eab7a571f3ed2dfba1a0 Mon Sep 17 00:00:00 2001 From: David Jia Date: Wed, 18 Mar 2026 14:10:05 -0700 Subject: [PATCH 4/8] move to SystemConstraints & add tests --- .../jetpackcamera/core/camera/CameraSystem.kt | 7 - .../core/camera/CameraXCameraSystem.kt | 34 ---- .../core/camera/test/FakeCameraSystem.kt | 7 - .../settings/model/Constraints.kt | 34 ++++ .../jetpackcamera/settings/ConstraintsTest.kt | 147 ++++++++++++++++++ 5 files changed, 181 insertions(+), 48 deletions(-) create mode 100644 data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt index 58ec5f449..b6821300e 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt @@ -131,13 +131,6 @@ interface CameraSystem { suspend fun setCaptureMode(captureMode: CaptureMode) - /** - * Returns a set of MIME types supported by the underlying camera hardware capabilities and JCA. - * - * @return A [Set] of strings representing the supported MIME types (e.g. image/jpeg, ""video/mp4). - */ - suspend fun getSupportedMimeTypes(): Set - /** * Represents the events required for screen flash. */ diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt index 93264e6b5..cd178595f 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt @@ -1034,40 +1034,6 @@ constructor( } } - /** - * Returns a set of MIME types supported by the underlying camera hardware capabilities and JCA. - * - * Empty set is returned if there are is available [CameraInfo]. - * The [CameraConstraints] of the default lens is used to determine the supported MIME types. - * image/jpeg is supported if the device supports JPEG format for the default stream. - * video/mp4 is supported if any dynamic ranges are supported. - * - * @return A [Set] of strings representing the supported MIME types. - */ - override suspend fun getSupportedMimeTypes(): Set { - val result = mutableSetOf() - currentSettings.value?.let { cameraAppSettings -> - val lensFacing = cameraAppSettings.cameraLensFacing - val cameraConstraints = systemConstraints.perLensConstraints[lensFacing] - if (cameraConstraints == null) { - return result - } - val streamConfig = cameraAppSettings.streamConfig - cameraConstraints.supportedImageFormatsMap[streamConfig]?.let { - if (it.contains( - ImageOutputFormat.JPEG - ) - ) { - result.add("image/jpeg") - } - } - if (cameraConstraints.supportedDynamicRanges.isNotEmpty()) { - result.add("video/mp4") - } - } - return result - } - private suspend fun handleLowLightBoostErrors() { currentCameraState.map { it.lowLightBoostState }.distinctUntilChanged().collect { state -> if (state is LowLightBoostState.Error) { diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt index f92becb3f..842afcf11 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraSystem.kt @@ -266,11 +266,4 @@ class FakeCameraSystem(defaultCameraSettings: CameraAppSettings = CameraAppSetti old.copy(captureMode = captureMode) } } - - /** - * Returns a fake list of supported MIME types. - */ - override suspend fun getSupportedMimeTypes(): Set { - return setOf("image/jpeg", "video/mp4") - } } diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt index 0dc3275c4..505a2c7c0 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt @@ -52,6 +52,40 @@ inline fun CameraSystemConstraints.forDevice( crossinline constraintSelector: (CameraConstraints) -> Iterable ) = perLensConstraints.values.asSequence().flatMap { constraintSelector(it) }.toSet() +/** + * Analyzes the camera system constraints to determine supported MIME types for each lens. + * + * This function iterates through all available lenses and checks their specific [CameraConstraints] + * to identify support for common media formats. + * + * Currently, it identifies: + * - `"image/jpeg"`: If the lens supports [ImageOutputFormat.JPEG] in a [StreamConfig.SINGLE_STREAM] setup. + * - `"video/mp4"`: If the lens supports at least one [DynamicRange] for video recording. + * + * @return A [Map] where each key is a [LensFacing] and the value is a [Set] of MIME type strings + * supported by that lens. If a lens has no constraints defined, its set will be empty. + */ +fun CameraSystemConstraints.getSupportedMimeTypes(): Map> { + val result = mutableMapOf>() + for (lensFacing in availableLenses) { + val mimeTypes = mutableSetOf() + result.put(lensFacing, mimeTypes) + val constraints = perLensConstraints[lensFacing] ?: continue + constraints.supportedImageFormatsMap[StreamConfig.SINGLE_STREAM]?.let { + if (it.contains( + ImageOutputFormat.JPEG + ) + ) { + mimeTypes.add("image/jpeg") + } + if (constraints.supportedDynamicRanges.isNotEmpty()) { + mimeTypes.add("video/mp4") + } + } + } + return result +} + /** * Defines the specific capabilities, limitations, and supported settings for a single camera lens. * diff --git a/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt b/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt new file mode 100644 index 000000000..cdde900ed --- /dev/null +++ b/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.jetpackcamera.settings + +import com.google.common.truth.Truth.assertThat +import com.google.jetpackcamera.model.DynamicRange +import com.google.jetpackcamera.model.ImageOutputFormat +import com.google.jetpackcamera.model.LensFacing +import com.google.jetpackcamera.model.StreamConfig +import com.google.jetpackcamera.settings.model.CameraConstraints +import com.google.jetpackcamera.settings.model.CameraSystemConstraints +import com.google.jetpackcamera.settings.model.getSupportedMimeTypes +import org.junit.Test + +class ConstraintsTest { + + @Test + fun getSupportedMimeTypes_returnsEmptyMap_whenNoLensesAvailable() { + val constraints = CameraSystemConstraints(availableLenses = emptyList()) + val result = constraints.getSupportedMimeTypes() + assertThat(result).isEmpty() + } + + @Test + fun getSupportedMimeTypes_returnsJpegAndMp4_forTypicalLens() { + val constraints = CameraSystemConstraints( + availableLenses = listOf(LensFacing.BACK), + perLensConstraints = mapOf( + LensFacing.BACK to createConstraints( + imageFormats = setOf(ImageOutputFormat.JPEG), + dynamicRanges = setOf(DynamicRange.SDR) + ) + ) + ) + + val result = constraints.getSupportedMimeTypes() + + assertThat(result[LensFacing.BACK]).containsExactly("image/jpeg", "video/mp4") + } + + @Test + fun getSupportedMimeTypes_onlyReturnsJpeg_whenNoDynamicRangesSupported() { + val constraints = CameraSystemConstraints( + availableLenses = listOf(LensFacing.BACK), + perLensConstraints = mapOf( + LensFacing.BACK to createConstraints( + imageFormats = setOf(ImageOutputFormat.JPEG), + dynamicRanges = emptySet() + ) + ) + ) + + val result = constraints.getSupportedMimeTypes() + + assertThat(result[LensFacing.BACK]).containsExactly("image/jpeg") + } + + @Test + fun getSupportedMimeTypes_onlyReturnsMp4_whenJpegNotSupported() { + val constraints = CameraSystemConstraints( + availableLenses = listOf(LensFacing.BACK), + perLensConstraints = mapOf( + LensFacing.BACK to createConstraints( + imageFormats = emptySet(), + dynamicRanges = setOf(DynamicRange.SDR) + ) + ) + ) + + val result = constraints.getSupportedMimeTypes() + + assertThat(result[LensFacing.BACK]).containsExactly("video/mp4") + } + + @Test + fun getSupportedMimeTypes_returnsEmptySet_whenNeitherSupported() { + val constraints = CameraSystemConstraints( + availableLenses = listOf(LensFacing.BACK), + perLensConstraints = mapOf( + LensFacing.BACK to createConstraints( + imageFormats = emptySet(), + dynamicRanges = emptySet() + ) + ) + ) + + val result = constraints.getSupportedMimeTypes() + + assertThat(result[LensFacing.BACK]).isEmpty() + } + + @Test + fun getSupportedMimeTypes_handlesMultipleLensesSeparately() { + val constraints = CameraSystemConstraints( + availableLenses = listOf(LensFacing.BACK, LensFacing.FRONT), + perLensConstraints = mapOf( + LensFacing.BACK to createConstraints( + imageFormats = setOf(ImageOutputFormat.JPEG), + dynamicRanges = setOf(DynamicRange.SDR) + ), + LensFacing.FRONT to createConstraints( + imageFormats = setOf(ImageOutputFormat.JPEG), + dynamicRanges = emptySet() + ) + ) + ) + + val result = constraints.getSupportedMimeTypes() + + assertThat(result[LensFacing.BACK]).containsExactly("image/jpeg", "video/mp4") + assertThat(result[LensFacing.FRONT]).containsExactly("image/jpeg") + } + + /** + * Helper to create CameraConstraints with specific formats/ranges for testing. + */ + private fun createConstraints( + imageFormats: Set, + dynamicRanges: Set + ): CameraConstraints { + return CameraConstraints( + supportedStabilizationModes = emptySet(), + supportedFixedFrameRates = emptySet(), + supportedDynamicRanges = dynamicRanges, + supportedVideoQualitiesMap = emptyMap(), + supportedImageFormatsMap = mapOf(StreamConfig.SINGLE_STREAM to imageFormats), + supportedIlluminants = emptySet(), + supportedFlashModes = emptySet(), + supportedZoomRange = null, + unsupportedStabilizationFpsMap = emptyMap(), + supportedTestPatterns = emptySet() + ) + } +} From 2d384dd0e5a9ab8997bf1b4a00825f4cd83ab37c Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 24 Mar 2026 15:39:26 -0700 Subject: [PATCH 5/8] add test runner --- .../java/com/google/jetpackcamera/settings/ConstraintsTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt b/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt index cdde900ed..a256d4b38 100644 --- a/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt +++ b/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt @@ -24,7 +24,10 @@ import com.google.jetpackcamera.settings.model.CameraConstraints import com.google.jetpackcamera.settings.model.CameraSystemConstraints import com.google.jetpackcamera.settings.model.getSupportedMimeTypes import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +@RunWith(JUnit4::class) class ConstraintsTest { @Test From e1039a93950c36691e018030ba4dc839a75428b0 Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 14 Apr 2026 09:40:51 -0700 Subject: [PATCH 6/8] Update CameraXCameraSystem.kt --- .../com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt index c8345d42f..ca4f4f0c5 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt @@ -79,7 +79,6 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.CameraConstraints import com.google.jetpackcamera.settings.model.CameraSystemConstraints import com.google.jetpackcamera.settings.model.forCurrentLens -import dagger.hilt.android.scopes.ViewModelScoped import java.io.File import java.io.FileNotFoundException import javax.inject.Inject @@ -103,7 +102,6 @@ private const val TAG = "CameraXCameraSystem" /** * CameraX based implementation for [CameraSystem] */ -@ViewModelScoped class CameraXCameraSystem @Inject constructor( From efb93b383f258481adf6635ec987abda6f0f30ed Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 14 Apr 2026 10:05:59 -0700 Subject: [PATCH 7/8] Update Constraints.kt --- .../java/com/google/jetpackcamera/settings/model/Constraints.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt index 601b58d09..9a254a3dd 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt @@ -80,7 +80,7 @@ fun CameraSystemConstraints.getSupportedMimeTypes(): Map ) { mimeTypes.add("image/jpeg") } - if (constraints.supportedDynamicRanges.isNotEmpty()) { + if (constraints.supportedVideoQualitiesMap[DynamicRange.SDR]?.isNotEmpty() == true) { mimeTypes.add("video/mp4") } } From c280e675cf223fc160fd18c39421a7abab2efd84 Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 14 Apr 2026 10:10:22 -0700 Subject: [PATCH 8/8] Update Constraints.kt --- .../java/com/google/jetpackcamera/settings/model/Constraints.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt index 9a254a3dd..601b58d09 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Constraints.kt @@ -80,7 +80,7 @@ fun CameraSystemConstraints.getSupportedMimeTypes(): Map ) { mimeTypes.add("image/jpeg") } - if (constraints.supportedVideoQualitiesMap[DynamicRange.SDR]?.isNotEmpty() == true) { + if (constraints.supportedDynamicRanges.isNotEmpty()) { mimeTypes.add("video/mp4") } }