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( 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 08dd8503e..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 @@ -54,6 +54,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 capabilities and limitations 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..a256d4b38 --- /dev/null +++ b/data/settings/src/test/java/com/google/jetpackcamera/settings/ConstraintsTest.kt @@ -0,0 +1,150 @@ +/* + * 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 +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +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() + ) + } +}