From 85fa9d325f21f392695fb66066ff80f2d65c8bf3 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Fri, 13 Feb 2026 02:08:00 +0000 Subject: [PATCH 1/2] Refactor: Decouple DataStore from Settings Module Extracted Proto DataStore implementation into a new :data:settings-datastore module. This improves modularity and separation of concerns by isolating the persistence layer. :data:settings now solely defines interfaces and handles constraints. # Conflicts: # data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt --- .../data/settingsdatastore/LocalSettingsRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt b/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt index 5e90a740b..376a16089 100644 --- a/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt +++ b/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt @@ -46,7 +46,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map /** - * Implementation of [com.google.jetpackcamera.settings.SettingsRepository] with locally stored settings. + * Implementation of [SettingsRepository] with locally stored settings. */ class LocalSettingsRepository @Inject constructor( private val jcaSettings: DataStore, @@ -221,4 +221,4 @@ class LocalSettingsRepository @Inject constructor( .build() } } -} +} \ No newline at end of file From 578ce72d5920c427dba12873c88221870ab432e9 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Fri, 13 Feb 2026 02:27:26 +0000 Subject: [PATCH 2/2] refactor: centralize proto/domain model and mapping Moves all proto/domain conversion logic into a dedicated mappers layer in core:model. This cleans up data models and LocalSettingsRepositor and improves testability and reusability of conversion logic. --- .../google/jetpackcamera/model/AspectRatio.kt | 19 - .../jetpackcamera/model/DebugSettings.kt | 71 +--- .../jetpackcamera/model/DynamicRange.kt | 25 +- .../jetpackcamera/model/ImageOutputFormat.kt | 25 +- .../google/jetpackcamera/model/LensFacing.kt | 23 - .../model/LowLightBoostPriority.kt | 31 +- .../jetpackcamera/model/StabilizationMode.kt | 21 +- .../google/jetpackcamera/model/TestPattern.kt | 62 --- .../jetpackcamera/model/VideoQuality.kt | 29 +- .../model/mappers/DebugSettingsMapper.kt | 51 +++ .../model/mappers/ProtoMappers.kt | 355 ++++++++++++++++ .../model/ProtoConversionTest.kt | 396 ++++++++++++++++++ ...LocalSettingsRepositoryInstrumentedTest.kt | 1 + .../LocalSettingsRepository.kt | 73 +--- .../navigation/DebugSettingsNavType.kt | 10 +- .../preview/navigation/PreviewNavigation.kt | 3 +- 16 files changed, 835 insertions(+), 360 deletions(-) create mode 100644 core/model/src/main/java/com/google/jetpackcamera/model/mappers/DebugSettingsMapper.kt create mode 100644 core/model/src/main/java/com/google/jetpackcamera/model/mappers/ProtoMappers.kt create mode 100644 core/model/src/test/java/com/google/jetpackcamera/model/ProtoConversionTest.kt diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/AspectRatio.kt b/core/model/src/main/java/com/google/jetpackcamera/model/AspectRatio.kt index 8dafa4a5f..8b96e1304 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/AspectRatio.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/AspectRatio.kt @@ -15,8 +15,6 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.AspectRatio as AspectRatioProto - enum class AspectRatio(val numerator: Int, val denominator: Int) { THREE_FOUR(3, 4), NINE_SIXTEEN(9, 16), @@ -31,21 +29,4 @@ enum class AspectRatio(val numerator: Int, val denominator: Int) { * Returns the landscape aspect ratio as a [Float]. */ fun toLandscapeFloat(): Float = denominator.toFloat() / numerator - - companion object { - - /** returns the AspectRatio enum equivalent of a provided AspectRatioProto */ - fun fromProto(aspectRatioProto: AspectRatioProto): AspectRatio { - return when (aspectRatioProto) { - AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN -> NINE_SIXTEEN - AspectRatioProto.ASPECT_RATIO_ONE_ONE -> ONE_ONE - - // defaults to 3:4 aspect ratio - AspectRatioProto.ASPECT_RATIO_THREE_FOUR, - AspectRatioProto.ASPECT_RATIO_UNDEFINED, - AspectRatioProto.UNRECOGNIZED - -> THREE_FOUR - } - } - } } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/DebugSettings.kt b/core/model/src/main/java/com/google/jetpackcamera/model/DebugSettings.kt index 25e05698e..7b36fe7d1 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/DebugSettings.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/DebugSettings.kt @@ -15,12 +15,6 @@ */ package com.google.jetpackcamera.model -import android.util.Base64 -import com.google.jetpackcamera.model.LensFacing.Companion.toProto -import com.google.jetpackcamera.model.TestPattern.Companion.toProto -import com.google.jetpackcamera.model.proto.DebugSettings as DebugSettingsProto -import com.google.jetpackcamera.model.proto.debugSettings as debugSettingsProto - /** * Data class for defining settings used in debug flows within the app. * @@ -35,67 +29,4 @@ data class DebugSettings( val isDebugModeEnabled: Boolean = false, val singleLensMode: LensFacing? = null, val testPattern: TestPattern = TestPattern.Off -) { - companion object { - /** - * Creates a [DebugSettings] domain model from its protobuf representation. - * - * @param proto The [DebugSettingsProto] instance. - * @return The corresponding [DebugSettings] instance. - */ - fun fromProto(proto: DebugSettingsProto): DebugSettings { - return DebugSettings( - isDebugModeEnabled = proto.isDebugModeEnabled, - singleLensMode = if (proto.hasSingleLensMode()) { - LensFacing.fromProto(proto.singleLensMode) - } else { - null - }, - testPattern = TestPattern.fromProto(proto.testPattern) - ) - } - - /** - * Converts a [DebugSettings] domain model to its protobuf representation. - * - * @receiver The [DebugSettings] instance to convert. - * @return The corresponding [DebugSettingsProto] instance. - */ - fun DebugSettings.toProto(): DebugSettingsProto = debugSettingsProto { - isDebugModeEnabled = this@toProto.isDebugModeEnabled - this@toProto.singleLensMode?.let { lensFacing -> - singleLensMode = lensFacing.toProto() - } - testPattern = this@toProto.testPattern.toProto() - } - - /** - * Parses the encoded byte array into a [DebugSettings] instance. - */ - fun parseFromByteArray(value: ByteArray): DebugSettings { - val protoValue = DebugSettingsProto.parseFrom(value) - return fromProto(protoValue) - } - - /** - * Parses the Base64 encoded string into a [DebugSettings] instance. - */ - fun parseFromString(value: String): DebugSettings { - val decodedBytes = Base64.decode(value, Base64.NO_WRAP) - return parseFromByteArray(decodedBytes) - } - - /** - * Encodes the [DebugSettings] data class into a byte array. - */ - fun DebugSettings.encodeAsByteArray(): ByteArray = this.toProto().toByteArray() - - /** - * Encodes the [DebugSettings] data class to a Base64 string. - */ - fun DebugSettings.encodeAsString(): String { - val protoValue = this.toProto() // Data class -> Proto - return Base64.encodeToString(protoValue.toByteArray(), Base64.NO_WRAP) - } - } -} +) diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/DynamicRange.kt b/core/model/src/main/java/com/google/jetpackcamera/model/DynamicRange.kt index ecc228741..8afdb1da3 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/DynamicRange.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/DynamicRange.kt @@ -14,33 +14,10 @@ * limitations under the License. */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.DynamicRange as DynamicRangeProto val DEFAULT_HDR_DYNAMIC_RANGE = DynamicRange.HLG10 enum class DynamicRange { SDR, - HLG10; - - companion object { - - /** returns the DynamicRangeType enum equivalent of a provided DynamicRangeTypeProto */ - fun fromProto(dynamicRangeProto: DynamicRangeProto): DynamicRange { - return when (dynamicRangeProto) { - DynamicRangeProto.DYNAMIC_RANGE_HLG10 -> HLG10 - - // Treat unrecognized and unspecified as SDR as a fallback - DynamicRangeProto.DYNAMIC_RANGE_SDR, - DynamicRangeProto.DYNAMIC_RANGE_UNSPECIFIED, - DynamicRangeProto.UNRECOGNIZED -> SDR - } - } - - fun DynamicRange.toProto(): DynamicRangeProto { - return when (this) { - SDR -> DynamicRangeProto.DYNAMIC_RANGE_SDR - HLG10 -> DynamicRangeProto.DYNAMIC_RANGE_HLG10 - } - } - } + HLG10 } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/ImageOutputFormat.kt b/core/model/src/main/java/com/google/jetpackcamera/model/ImageOutputFormat.kt index 8fa219ab8..c19a4826e 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/ImageOutputFormat.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/ImageOutputFormat.kt @@ -15,32 +15,9 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.ImageOutputFormat as ImageOutputFormatProto - val DEFAULT_HDR_IMAGE_OUTPUT = ImageOutputFormat.JPEG_ULTRA_HDR enum class ImageOutputFormat { JPEG, - JPEG_ULTRA_HDR; - - companion object { - - /** returns the DynamicRangeType enum equivalent of a provided DynamicRangeTypeProto */ - fun fromProto(imageOutputFormatProto: ImageOutputFormatProto): ImageOutputFormat { - return when (imageOutputFormatProto) { - ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR -> JPEG_ULTRA_HDR - - // Treat unrecognized as JPEG as a fallback - ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG, - ImageOutputFormatProto.UNRECOGNIZED -> JPEG - } - } - - fun ImageOutputFormat.toProto(): ImageOutputFormatProto { - return when (this) { - JPEG -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG - JPEG_ULTRA_HDR -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR - } - } - } + JPEG_ULTRA_HDR } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/LensFacing.kt b/core/model/src/main/java/com/google/jetpackcamera/model/LensFacing.kt index 4aa5a27b5..b72c807b0 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/LensFacing.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/LensFacing.kt @@ -15,8 +15,6 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.LensFacing as LensFacingProto - enum class LensFacing { BACK, FRONT; @@ -27,25 +25,4 @@ enum class LensFacing { BACK -> FRONT } } - - companion object { - - /** returns the LensFacing enum equivalent of a provided LensFacingProto */ - fun fromProto(lensFacingProto: LensFacingProto): LensFacing { - return when (lensFacingProto) { - LensFacingProto.LENS_FACING_BACK -> BACK - - // Treat unrecognized as front as a fallback - LensFacingProto.LENS_FACING_FRONT, - LensFacingProto.UNRECOGNIZED -> FRONT - } - } - - fun LensFacing.toProto(): LensFacingProto { - return when (this) { - BACK -> LensFacingProto.LENS_FACING_BACK - FRONT -> LensFacingProto.LENS_FACING_FRONT - } - } - } } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/LowLightBoostPriority.kt b/core/model/src/main/java/com/google/jetpackcamera/model/LowLightBoostPriority.kt index eb6aa7367..38da49b84 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/LowLightBoostPriority.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/LowLightBoostPriority.kt @@ -15,36 +15,7 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.LowLightBoostPriority as LowLightBoostPriorityProto - enum class LowLightBoostPriority { PRIORITIZE_AE_MODE, - PRIORITIZE_GOOGLE_PLAY_SERVICES; - - companion object { - /** - * Returns the [LowLightBoostPriority] enum equivalent of a provided [LowLightBoostPriorityProto]. - * - * @param lowLightBoostPriorityProto The proto to convert from. - * @return The converted [LowLightBoostPriority]. - */ - fun fromProto( - lowLightBoostPriorityProto: LowLightBoostPriorityProto - ): LowLightBoostPriority { - return when (lowLightBoostPriorityProto) { - LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE -> PRIORITIZE_AE_MODE - LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES -> - PRIORITIZE_GOOGLE_PLAY_SERVICES - LowLightBoostPriorityProto.UNRECOGNIZED -> PRIORITIZE_AE_MODE // Default to AE mode - } - } - - fun LowLightBoostPriority.toProto(): LowLightBoostPriorityProto { - return when (this) { - PRIORITIZE_AE_MODE -> LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE - PRIORITIZE_GOOGLE_PLAY_SERVICES -> - LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES - } - } - } + PRIORITIZE_GOOGLE_PLAY_SERVICES } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/StabilizationMode.kt b/core/model/src/main/java/com/google/jetpackcamera/model/StabilizationMode.kt index bf0dfbe1d..13aac311d 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/StabilizationMode.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/StabilizationMode.kt @@ -15,8 +15,6 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.StabilizationMode as StabilizationModeProto - /** Enum class representing the device's supported stabilization configurations. */ enum class StabilizationMode { /** Stabilization off */ @@ -36,22 +34,5 @@ enum class StabilizationMode { HIGH_QUALITY, /** Optical Stabilization (OIS) */ - OPTICAL; - - companion object { - /** returns the AspectRatio enum equivalent of a provided AspectRatioProto */ - fun fromProto(stabilizationModeProto: StabilizationModeProto): StabilizationMode = - when (stabilizationModeProto) { - StabilizationModeProto.STABILIZATION_MODE_OFF -> OFF - StabilizationModeProto.STABILIZATION_MODE_ON -> ON - StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY -> HIGH_QUALITY - StabilizationModeProto.STABILIZATION_MODE_OPTICAL -> OPTICAL - - // Default to AUTO - StabilizationModeProto.STABILIZATION_MODE_UNDEFINED, - StabilizationModeProto.UNRECOGNIZED, - StabilizationModeProto.STABILIZATION_MODE_AUTO - -> AUTO - } - } + OPTICAL } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/TestPattern.kt b/core/model/src/main/java/com/google/jetpackcamera/model/TestPattern.kt index 15fc3e2f4..d758db1ee 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/TestPattern.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/TestPattern.kt @@ -15,16 +15,6 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.TestPattern as ProtoTestPattern -import com.google.jetpackcamera.model.proto.TestPattern.PatternCase -import com.google.jetpackcamera.model.proto.testPattern as protoTestPattern -import com.google.jetpackcamera.model.proto.testPatternColorBars -import com.google.jetpackcamera.model.proto.testPatternColorBarsFadeToGray -import com.google.jetpackcamera.model.proto.testPatternCustom1 -import com.google.jetpackcamera.model.proto.testPatternOff -import com.google.jetpackcamera.model.proto.testPatternPN9 -import com.google.jetpackcamera.model.proto.testPatternSolidColor - /** * Represents a test pattern to replace sensor pixel data. * @@ -179,56 +169,4 @@ sealed interface TestPattern { ) } } - - companion object { - /** - * Converts a [TestPattern] sealed interface instance to its Protocol Buffer representation - * ([ProtoTestPattern]). - */ - fun TestPattern.toProto(): ProtoTestPattern { - return protoTestPattern { - when (val pattern = this@toProto) { - is Off -> off = testPatternOff {} - is ColorBars -> colorBars = testPatternColorBars {} - is ColorBarsFadeToGray -> - colorBarsFadeToGray = testPatternColorBarsFadeToGray {} - is PN9 -> pn9 = testPatternPN9 {} - is Custom1 -> custom1 = testPatternCustom1 {} - is SolidColor -> solidColor = testPatternSolidColor { - red = pattern.red.toInt() - greenEven = pattern.greenEven.toInt() - greenOdd = pattern.greenOdd.toInt() - blue = pattern.blue.toInt() - } - } - } - } - - /** - * Converts a [ProtoTestPattern] Protocol Buffer message to its Kotlin [TestPattern] sealed - * interface representation. - */ - fun fromProto(proto: ProtoTestPattern): TestPattern { - return when (proto.patternCase) { - PatternCase.OFF, - PatternCase.PATTERN_NOT_SET -> { - // Default to Off if the oneof is not set - Off - } - PatternCase.COLOR_BARS -> ColorBars - PatternCase.COLOR_BARS_FADE_TO_GRAY -> ColorBarsFadeToGray - PatternCase.PN9 -> PN9 - PatternCase.CUSTOM1 -> Custom1 - PatternCase.SOLID_COLOR -> { - val protoSolidColor = proto.solidColor - SolidColor( - red = protoSolidColor.red.toUInt(), - greenEven = protoSolidColor.greenEven.toUInt(), - greenOdd = protoSolidColor.greenOdd.toUInt(), - blue = protoSolidColor.blue.toUInt() - ) - } - } - } - } } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/VideoQuality.kt b/core/model/src/main/java/com/google/jetpackcamera/model/VideoQuality.kt index 143b7d6a2..5c52aa61b 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/VideoQuality.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/VideoQuality.kt @@ -15,37 +15,10 @@ */ package com.google.jetpackcamera.model -import com.google.jetpackcamera.model.proto.VideoQuality as VideoQualityProto - enum class VideoQuality { UNSPECIFIED, SD, HD, FHD, - UHD; - - companion object { - /** returns the VideoQuality enum equivalent of a provided VideoQualityProto */ - fun fromProto(videoQualityProto: VideoQualityProto): VideoQuality { - return when (videoQualityProto) { - VideoQualityProto.VIDEO_QUALITY_SD -> SD - VideoQualityProto.VIDEO_QUALITY_HD -> HD - VideoQualityProto.VIDEO_QUALITY_FHD -> FHD - VideoQualityProto.VIDEO_QUALITY_UHD -> UHD - VideoQualityProto.VIDEO_QUALITY_UNSPECIFIED, - VideoQualityProto.UNRECOGNIZED - -> UNSPECIFIED - } - } - - fun VideoQuality.toProto(): VideoQualityProto { - return when (this) { - UNSPECIFIED -> VideoQualityProto.VIDEO_QUALITY_UNSPECIFIED - SD -> VideoQualityProto.VIDEO_QUALITY_SD - HD -> VideoQualityProto.VIDEO_QUALITY_HD - FHD -> VideoQualityProto.VIDEO_QUALITY_FHD - UHD -> VideoQualityProto.VIDEO_QUALITY_UHD - } - } - } + UHD } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/mappers/DebugSettingsMapper.kt b/core/model/src/main/java/com/google/jetpackcamera/model/mappers/DebugSettingsMapper.kt new file mode 100644 index 000000000..a09d887f0 --- /dev/null +++ b/core/model/src/main/java/com/google/jetpackcamera/model/mappers/DebugSettingsMapper.kt @@ -0,0 +1,51 @@ +/* + * 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.model.mappers + +import android.util.Base64 +import com.google.jetpackcamera.model.DebugSettings +import com.google.jetpackcamera.model.proto.DebugSettings as DebugSettingsProto + +object DebugSettingsMapper { + /** + * Parses the encoded byte array into a [DebugSettings] instance. + */ + fun parseFromByteArray(value: ByteArray): DebugSettings { + val protoValue = DebugSettingsProto.parseFrom(value) + return protoValue.toDomain() + } + + /** + * Parses the Base64 encoded string into a [DebugSettings] instance. + */ + fun parseFromString(value: String): DebugSettings { + val decodedBytes = Base64.decode(value, Base64.NO_WRAP) + return parseFromByteArray(decodedBytes) + } + + /** + * Encodes the [DebugSettings] data class into a byte array. + */ + fun DebugSettings.encodeAsByteArray(): ByteArray = this.toProto().toByteArray() + + /** + * Encodes the [DebugSettings] data class to a Base64 string. + */ + fun DebugSettings.encodeAsString(): String { + val protoValue = this.toProto() // Data class -> Proto + return Base64.encodeToString(protoValue.toByteArray(), Base64.NO_WRAP) + } +} diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/mappers/ProtoMappers.kt b/core/model/src/main/java/com/google/jetpackcamera/model/mappers/ProtoMappers.kt new file mode 100644 index 000000000..7046f7575 --- /dev/null +++ b/core/model/src/main/java/com/google/jetpackcamera/model/mappers/ProtoMappers.kt @@ -0,0 +1,355 @@ +/* + * 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.model.mappers + +import com.google.jetpackcamera.model.AspectRatio +import com.google.jetpackcamera.model.DebugSettings +import com.google.jetpackcamera.model.DynamicRange +import com.google.jetpackcamera.model.DynamicRange.HLG10 +import com.google.jetpackcamera.model.DynamicRange.SDR +import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.ImageOutputFormat +import com.google.jetpackcamera.model.ImageOutputFormat.JPEG +import com.google.jetpackcamera.model.ImageOutputFormat.JPEG_ULTRA_HDR +import com.google.jetpackcamera.model.LensFacing +import com.google.jetpackcamera.model.LensFacing.BACK +import com.google.jetpackcamera.model.LensFacing.FRONT +import com.google.jetpackcamera.model.LowLightBoostPriority +import com.google.jetpackcamera.model.LowLightBoostPriority.PRIORITIZE_AE_MODE +import com.google.jetpackcamera.model.LowLightBoostPriority.PRIORITIZE_GOOGLE_PLAY_SERVICES +import com.google.jetpackcamera.model.StabilizationMode +import com.google.jetpackcamera.model.StabilizationMode.AUTO +import com.google.jetpackcamera.model.StabilizationMode.HIGH_QUALITY +import com.google.jetpackcamera.model.StabilizationMode.OFF +import com.google.jetpackcamera.model.StabilizationMode.ON +import com.google.jetpackcamera.model.StabilizationMode.OPTICAL +import com.google.jetpackcamera.model.StreamConfig +import com.google.jetpackcamera.model.TestPattern +import com.google.jetpackcamera.model.TestPattern.ColorBars +import com.google.jetpackcamera.model.TestPattern.ColorBarsFadeToGray +import com.google.jetpackcamera.model.TestPattern.Custom1 +import com.google.jetpackcamera.model.TestPattern.Off +import com.google.jetpackcamera.model.TestPattern.PN9 +import com.google.jetpackcamera.model.TestPattern.SolidColor +import com.google.jetpackcamera.model.VideoQuality +import com.google.jetpackcamera.model.VideoQuality.FHD +import com.google.jetpackcamera.model.VideoQuality.HD +import com.google.jetpackcamera.model.VideoQuality.SD +import com.google.jetpackcamera.model.VideoQuality.UHD +import com.google.jetpackcamera.model.VideoQuality.UNSPECIFIED +import com.google.jetpackcamera.model.proto.AspectRatio as AspectRatioProto +import com.google.jetpackcamera.model.proto.DebugSettings as DebugSettingsProto +import com.google.jetpackcamera.model.proto.DynamicRange as DynamicRangeProto +import com.google.jetpackcamera.model.proto.FlashMode as FlashModeProto +import com.google.jetpackcamera.model.proto.ImageOutputFormat as ImageOutputFormatProto +import com.google.jetpackcamera.model.proto.LensFacing as LensFacingProto +import com.google.jetpackcamera.model.proto.LowLightBoostPriority as LowLightBoostPriorityProto +import com.google.jetpackcamera.model.proto.StabilizationMode as StabilizationModeProto +import com.google.jetpackcamera.model.proto.StreamConfig as StreamConfigProto +import com.google.jetpackcamera.model.proto.TestPattern as ProtoTestPattern +import com.google.jetpackcamera.model.proto.TestPattern.PatternCase +import com.google.jetpackcamera.model.proto.VideoQuality as VideoQualityProto +import com.google.jetpackcamera.model.proto.debugSettings as debugSettingsProto +import com.google.jetpackcamera.model.proto.testPattern as protoTestPattern +import com.google.jetpackcamera.model.proto.testPatternColorBars +import com.google.jetpackcamera.model.proto.testPatternColorBarsFadeToGray +import com.google.jetpackcamera.model.proto.testPatternCustom1 +import com.google.jetpackcamera.model.proto.testPatternOff +import com.google.jetpackcamera.model.proto.testPatternPN9 +import com.google.jetpackcamera.model.proto.testPatternSolidColor + +/** + * Converts an [AspectRatio] enum to its corresponding [AspectRatioProto] representation. + */ +fun AspectRatio.toProto(): AspectRatioProto = when (this) { + AspectRatio.NINE_SIXTEEN -> AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN + AspectRatio.THREE_FOUR -> AspectRatioProto.ASPECT_RATIO_THREE_FOUR + AspectRatio.ONE_ONE -> AspectRatioProto.ASPECT_RATIO_ONE_ONE +} + +/** returns the AspectRatio enum equivalent of a provided AspectRatioProto */ +fun AspectRatioProto.toDomain(): AspectRatio { + return when (this) { + AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN -> AspectRatio.NINE_SIXTEEN + AspectRatioProto.ASPECT_RATIO_ONE_ONE -> AspectRatio.ONE_ONE + + // defaults to 3:4 aspect ratio + AspectRatioProto.ASPECT_RATIO_THREE_FOUR, + AspectRatioProto.ASPECT_RATIO_UNDEFINED, + AspectRatioProto.UNRECOGNIZED + -> AspectRatio.THREE_FOUR + } +} + +/** + * Creates a [DebugSettings] domain model from its protobuf representation. + * + * @return The corresponding [DebugSettings] instance. + */ +fun DebugSettingsProto.toDomain(): DebugSettings { + return DebugSettings( + isDebugModeEnabled = this.isDebugModeEnabled, + singleLensMode = if (this.hasSingleLensMode()) { + this.singleLensMode.toDomain() + } else { + null + }, + testPattern = this.testPattern.toDomain() + ) +} + +/** + * Converts a [DebugSettings] domain model to its protobuf representation. + * + * @receiver The [DebugSettings] instance to convert. + * @return The corresponding [DebugSettingsProto] instance. + */ +fun DebugSettings.toProto(): DebugSettingsProto = debugSettingsProto { + isDebugModeEnabled = this@toProto.isDebugModeEnabled + this@toProto.singleLensMode?.let { lensFacing -> + singleLensMode = lensFacing.toProto() + } + testPattern = this@toProto.testPattern.toProto() +} + +/** returns the DynamicRangeType enum equivalent of a provided DynamicRangeTypeProto */ +fun DynamicRangeProto.toDomain(): DynamicRange { + return when (this) { + DynamicRangeProto.DYNAMIC_RANGE_HLG10 -> HLG10 + + // Treat unrecognized and unspecified as SDR as a fallback + DynamicRangeProto.DYNAMIC_RANGE_SDR, + DynamicRangeProto.DYNAMIC_RANGE_UNSPECIFIED, + DynamicRangeProto.UNRECOGNIZED -> SDR + } +} + +/** + * Converts a [DynamicRange] enum to its corresponding [DynamicRangeProto] representation. + */ +fun DynamicRange.toProto(): DynamicRangeProto { + return when (this) { + SDR -> DynamicRangeProto.DYNAMIC_RANGE_SDR + HLG10 -> DynamicRangeProto.DYNAMIC_RANGE_HLG10 + } +} + +/** + * Converts a [FlashMode] enum to its corresponding [FlashModeProto] representation. + */ +fun FlashMode.toProto(): FlashModeProto = when (this) { + FlashMode.AUTO -> FlashModeProto.FLASH_MODE_AUTO + FlashMode.ON -> FlashModeProto.FLASH_MODE_ON + FlashMode.OFF -> FlashModeProto.FLASH_MODE_OFF + FlashMode.LOW_LIGHT_BOOST -> FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST +} + +/** + * Converts a [FlashModeProto] to its corresponding [FlashMode] enum representation. + */ +fun FlashModeProto.toDomain(): FlashMode = when (this) { + FlashModeProto.FLASH_MODE_AUTO -> FlashMode.AUTO + FlashModeProto.FLASH_MODE_ON -> FlashMode.ON + FlashModeProto.FLASH_MODE_OFF -> FlashMode.OFF + FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST -> FlashMode.LOW_LIGHT_BOOST + else -> FlashMode.OFF +} + +/** returns the DynamicRangeType enum equivalent of a provided DynamicRangeTypeProto */ +fun ImageOutputFormatProto.toDomain(): ImageOutputFormat { + return when (this) { + ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR -> JPEG_ULTRA_HDR + + // Treat unrecognized as JPEG as a fallback + ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG, + ImageOutputFormatProto.UNRECOGNIZED -> JPEG + } +} + +/** + * Converts an [ImageOutputFormat] enum to its corresponding [ImageOutputFormatProto] representation. + */ +fun ImageOutputFormat.toProto(): ImageOutputFormatProto { + return when (this) { + JPEG -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG + JPEG_ULTRA_HDR -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR + } +} + +/** returns the LensFacing enum equivalent of a provided LensFacingProto */ +fun LensFacingProto.toDomain(): LensFacing { + return when (this) { + LensFacingProto.LENS_FACING_FRONT -> FRONT + + // Treat unrecognized as back as a fallback + LensFacingProto.LENS_FACING_BACK, + LensFacingProto.UNRECOGNIZED -> BACK + } +} + +/** + * Converts a [LensFacing] enum to its corresponding [LensFacingProto] representation. + */ +fun LensFacing.toProto(): LensFacingProto { + return when (this) { + BACK -> LensFacingProto.LENS_FACING_BACK + FRONT -> LensFacingProto.LENS_FACING_FRONT + } +} + +/** + * Returns the [LowLightBoostPriority] enum equivalent of a provided [LowLightBoostPriorityProto]. + * + * @return The converted [LowLightBoostPriority]. + */ +fun LowLightBoostPriorityProto.toDomain(): LowLightBoostPriority { + return when (this) { + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE -> PRIORITIZE_AE_MODE + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES -> + PRIORITIZE_GOOGLE_PLAY_SERVICES + + LowLightBoostPriorityProto.UNRECOGNIZED -> PRIORITIZE_AE_MODE // Default to AE mode + } +} + +/** + * Converts a [LowLightBoostPriority] enum to its corresponding [LowLightBoostPriorityProto] + * representation. + */ +fun LowLightBoostPriority.toProto(): LowLightBoostPriorityProto { + return when (this) { + PRIORITIZE_AE_MODE -> LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE + PRIORITIZE_GOOGLE_PLAY_SERVICES -> + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES + } +} + +/** + * Converts a [StabilizationMode] enum to its corresponding [StabilizationModeProto] representation. + */ +fun StabilizationMode.toProto(): StabilizationModeProto = when (this) { + StabilizationMode.OFF -> StabilizationModeProto.STABILIZATION_MODE_OFF + StabilizationMode.AUTO -> StabilizationModeProto.STABILIZATION_MODE_AUTO + StabilizationMode.ON -> StabilizationModeProto.STABILIZATION_MODE_ON + StabilizationMode.HIGH_QUALITY -> StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY + StabilizationMode.OPTICAL -> StabilizationModeProto.STABILIZATION_MODE_OPTICAL +} + +/** returns the AspectRatio enum equivalent of a provided AspectRatioProto */ +fun StabilizationModeProto.toDomain(): StabilizationMode = when (this) { + StabilizationModeProto.STABILIZATION_MODE_OFF -> OFF + StabilizationModeProto.STABILIZATION_MODE_ON -> ON + StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY -> HIGH_QUALITY + StabilizationModeProto.STABILIZATION_MODE_OPTICAL -> OPTICAL + + // Default to AUTO + StabilizationModeProto.STABILIZATION_MODE_UNDEFINED, + StabilizationModeProto.UNRECOGNIZED, + StabilizationModeProto.STABILIZATION_MODE_AUTO + -> AUTO +} + +/** + * Converts a [StreamConfig] enum to its corresponding [StreamConfigProto] representation. + */ +fun StreamConfig.toProto(): StreamConfigProto = when (this) { + StreamConfig.MULTI_STREAM -> StreamConfigProto.STREAM_CONFIG_MULTI_STREAM + StreamConfig.SINGLE_STREAM -> StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM +} + +/** + * Converts a [StreamConfigProto] to its corresponding [StreamConfig] enum representation. + */ +fun StreamConfigProto.toDomain(): StreamConfig = when (this) { + StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM -> StreamConfig.SINGLE_STREAM + StreamConfigProto.STREAM_CONFIG_MULTI_STREAM -> StreamConfig.MULTI_STREAM + else -> StreamConfig.MULTI_STREAM +} + +/** + * Converts a [TestPattern] sealed interface instance to its Protocol Buffer representation + * ([ProtoTestPattern]). + */ +fun TestPattern.toProto(): ProtoTestPattern { + return protoTestPattern { + when (val pattern = this@toProto) { + is Off -> off = testPatternOff {} + is ColorBars -> colorBars = testPatternColorBars {} + is ColorBarsFadeToGray -> + colorBarsFadeToGray = testPatternColorBarsFadeToGray {} + + is PN9 -> pn9 = testPatternPN9 {} + is Custom1 -> custom1 = testPatternCustom1 {} + is SolidColor -> solidColor = testPatternSolidColor { + red = pattern.red.toInt() + greenEven = pattern.greenEven.toInt() + greenOdd = pattern.greenOdd.toInt() + blue = pattern.blue.toInt() + } + } + } +} + +/** + * Converts a [ProtoTestPattern] Protocol Buffer message to its Kotlin [TestPattern] sealed + * interface representation. + */ +fun ProtoTestPattern.toDomain(): TestPattern { + return when (this.patternCase) { + PatternCase.OFF, + PatternCase.PATTERN_NOT_SET -> { + // Default to Off if the oneof is not set + Off + } + + PatternCase.COLOR_BARS -> ColorBars + PatternCase.COLOR_BARS_FADE_TO_GRAY -> ColorBarsFadeToGray + PatternCase.PN9 -> PN9 + PatternCase.CUSTOM1 -> Custom1 + PatternCase.SOLID_COLOR -> { + val protoSolidColor = this.solidColor + SolidColor( + red = protoSolidColor.red.toUInt(), + greenEven = protoSolidColor.greenEven.toUInt(), + greenOdd = protoSolidColor.greenOdd.toUInt(), + blue = protoSolidColor.blue.toUInt() + ) + } + } +} + +/** returns the VideoQuality enum equivalent of a provided VideoQualityProto */ +fun VideoQualityProto.toDomain(): VideoQuality { + return when (this) { + VideoQualityProto.VIDEO_QUALITY_SD -> SD + VideoQualityProto.VIDEO_QUALITY_HD -> HD + VideoQualityProto.VIDEO_QUALITY_FHD -> FHD + VideoQualityProto.VIDEO_QUALITY_UHD -> UHD + VideoQualityProto.VIDEO_QUALITY_UNSPECIFIED, + VideoQualityProto.UNRECOGNIZED + -> UNSPECIFIED + } +} + +fun VideoQuality.toProto(): VideoQualityProto { + return when (this) { + UNSPECIFIED -> VideoQualityProto.VIDEO_QUALITY_UNSPECIFIED + SD -> VideoQualityProto.VIDEO_QUALITY_SD + HD -> VideoQualityProto.VIDEO_QUALITY_HD + FHD -> VideoQualityProto.VIDEO_QUALITY_FHD + UHD -> VideoQualityProto.VIDEO_QUALITY_UHD + } +} diff --git a/core/model/src/test/java/com/google/jetpackcamera/model/ProtoConversionTest.kt b/core/model/src/test/java/com/google/jetpackcamera/model/ProtoConversionTest.kt new file mode 100644 index 000000000..b105aab2a --- /dev/null +++ b/core/model/src/test/java/com/google/jetpackcamera/model/ProtoConversionTest.kt @@ -0,0 +1,396 @@ +/* + * 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.model + +import com.google.common.truth.Truth +import com.google.jetpackcamera.model.mappers.toDomain +import com.google.jetpackcamera.model.mappers.toProto +import com.google.jetpackcamera.model.proto.AspectRatio as AspectRatioProto +import com.google.jetpackcamera.model.proto.DebugSettings as DebugSettingsProto +import com.google.jetpackcamera.model.proto.DynamicRange as DynamicRangeProto +import com.google.jetpackcamera.model.proto.FlashMode as FlashModeProto +import com.google.jetpackcamera.model.proto.ImageOutputFormat as ImageOutputFormatProto +import com.google.jetpackcamera.model.proto.LensFacing as LensFacingProto +import com.google.jetpackcamera.model.proto.LowLightBoostPriority as LowLightBoostPriorityProto +import com.google.jetpackcamera.model.proto.StabilizationMode as StabilizationModeProto +import com.google.jetpackcamera.model.proto.StreamConfig as StreamConfigProto +import com.google.jetpackcamera.model.proto.VideoQuality as VideoQualityProto +import com.google.jetpackcamera.model.proto.debugSettings as debugSettingsProto +import com.google.jetpackcamera.model.proto.testPattern as protoTestPattern +import com.google.jetpackcamera.model.proto.testPatternColorBars +import com.google.jetpackcamera.model.proto.testPatternPN9 +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class ProtoConversionTest { + @Test + fun dynamicRange_convertsToCorrectProto() { + val correctConversions = { dynamicRange: DynamicRange -> + when (dynamicRange) { + DynamicRange.SDR -> DynamicRangeProto.DYNAMIC_RANGE_SDR + DynamicRange.HLG10 -> DynamicRangeProto.DYNAMIC_RANGE_HLG10 + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun dynamicRangeProto_convertsToCorrectDynamicRange() { + val correctConversions = { dynamicRangeProto: DynamicRangeProto -> + when (dynamicRangeProto) { + DynamicRangeProto.DYNAMIC_RANGE_SDR, + DynamicRangeProto.UNRECOGNIZED, + DynamicRangeProto.DYNAMIC_RANGE_UNSPECIFIED + -> DynamicRange.SDR + + DynamicRangeProto.DYNAMIC_RANGE_HLG10 -> DynamicRange.HLG10 + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun imageOutputFormat_convertsToCorrectProto() { + val correctConversions = { imageOutputFormat: ImageOutputFormat -> + when (imageOutputFormat) { + ImageOutputFormat.JPEG -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG + ImageOutputFormat.JPEG_ULTRA_HDR + -> ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun imageOutputFormatProto_convertsToCorrectImageOutputFormat() { + val correctConversions = { imageOutputFormatProto: ImageOutputFormatProto -> + when (imageOutputFormatProto) { + ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG, + ImageOutputFormatProto.UNRECOGNIZED + -> ImageOutputFormat.JPEG + + ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR + -> ImageOutputFormat.JPEG_ULTRA_HDR + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun aspectRatio_convertsToCorrectProto() { + val correctConversions = { aspectRatio: AspectRatio -> + when (aspectRatio) { + AspectRatio.NINE_SIXTEEN -> AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN + AspectRatio.THREE_FOUR -> AspectRatioProto.ASPECT_RATIO_THREE_FOUR + AspectRatio.ONE_ONE -> AspectRatioProto.ASPECT_RATIO_ONE_ONE + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun aspectRatioProto_convertsToCorrectAspectRatio() { + val correctConversions = { aspectRatioProto: AspectRatioProto -> + when (aspectRatioProto) { + AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN -> AspectRatio.NINE_SIXTEEN + AspectRatioProto.ASPECT_RATIO_THREE_FOUR -> AspectRatio.THREE_FOUR + AspectRatioProto.ASPECT_RATIO_ONE_ONE -> AspectRatio.ONE_ONE + else -> AspectRatio.THREE_FOUR // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun lensFacing_convertsToCorrectProto() { + val correctConversions = { lensFacing: LensFacing -> + when (lensFacing) { + LensFacing.FRONT -> LensFacingProto.LENS_FACING_FRONT + LensFacing.BACK -> LensFacingProto.LENS_FACING_BACK + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun lensFacingProto_convertsToCorrectLensFacing() { + val correctConversions = { lensFacingProto: LensFacingProto -> + when (lensFacingProto) { + LensFacingProto.LENS_FACING_FRONT -> LensFacing.FRONT + LensFacingProto.LENS_FACING_BACK -> LensFacing.BACK + else -> LensFacing.BACK // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun lowLightBoostPriority_convertsToCorrectProto() { + val correctConversions = { lowLightBoostPriority: LowLightBoostPriority -> + when (lowLightBoostPriority) { + LowLightBoostPriority.PRIORITIZE_AE_MODE -> + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE + LowLightBoostPriority.PRIORITIZE_GOOGLE_PLAY_SERVICES -> + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun lowLightBoostPriorityProto_convertsToCorrectLowLightBoostPriority() { + val correctConversions = { lowLightBoostPriorityProto: LowLightBoostPriorityProto -> + when (lowLightBoostPriorityProto) { + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_AE_MODE -> + LowLightBoostPriority.PRIORITIZE_AE_MODE + LowLightBoostPriorityProto.LOW_LIGHT_BOOST_PRIORITY_GOOGLE_PLAY_SERVICES -> + LowLightBoostPriority.PRIORITIZE_GOOGLE_PLAY_SERVICES + else -> LowLightBoostPriority.PRIORITIZE_AE_MODE // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun stabilizationMode_convertsToCorrectProto() { + val correctConversions = { stabilizationMode: StabilizationMode -> + when (stabilizationMode) { + StabilizationMode.OFF -> StabilizationModeProto.STABILIZATION_MODE_OFF + StabilizationMode.ON -> StabilizationModeProto.STABILIZATION_MODE_ON + StabilizationMode.HIGH_QUALITY -> + StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY + StabilizationMode.OPTICAL -> StabilizationModeProto.STABILIZATION_MODE_OPTICAL + StabilizationMode.AUTO -> StabilizationModeProto.STABILIZATION_MODE_AUTO + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun stabilizationModeProto_convertsToCorrectStabilizationMode() { + val correctConversions = { stabilizationModeProto: StabilizationModeProto -> + when (stabilizationModeProto) { + StabilizationModeProto.STABILIZATION_MODE_OFF -> StabilizationMode.OFF + StabilizationModeProto.STABILIZATION_MODE_ON -> StabilizationMode.ON + StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY -> + StabilizationMode.HIGH_QUALITY + StabilizationModeProto.STABILIZATION_MODE_OPTICAL -> StabilizationMode.OPTICAL + else -> StabilizationMode.AUTO // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun videoQuality_convertsToCorrectProto() { + val correctConversions = { videoQuality: VideoQuality -> + when (videoQuality) { + VideoQuality.SD -> VideoQualityProto.VIDEO_QUALITY_SD + VideoQuality.HD -> VideoQualityProto.VIDEO_QUALITY_HD + VideoQuality.FHD -> VideoQualityProto.VIDEO_QUALITY_FHD + VideoQuality.UHD -> VideoQualityProto.VIDEO_QUALITY_UHD + VideoQuality.UNSPECIFIED -> VideoQualityProto.VIDEO_QUALITY_UNSPECIFIED + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun videoQualityProto_convertsToCorrectVideoQuality() { + val correctConversions = { videoQualityProto: VideoQualityProto -> + when (videoQualityProto) { + VideoQualityProto.VIDEO_QUALITY_SD -> VideoQuality.SD + VideoQualityProto.VIDEO_QUALITY_HD -> VideoQuality.HD + VideoQualityProto.VIDEO_QUALITY_FHD -> VideoQuality.FHD + VideoQualityProto.VIDEO_QUALITY_UHD -> VideoQuality.UHD + else -> VideoQuality.UNSPECIFIED // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun flashMode_convertsToCorrectProto() { + val correctConversions = { flashMode: FlashMode -> + when (flashMode) { + FlashMode.AUTO -> FlashModeProto.FLASH_MODE_AUTO + FlashMode.ON -> FlashModeProto.FLASH_MODE_ON + FlashMode.OFF -> FlashModeProto.FLASH_MODE_OFF + FlashMode.LOW_LIGHT_BOOST -> FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun flashModeProto_convertsToCorrectFlashMode() { + val correctConversions = { flashModeProto: FlashModeProto -> + when (flashModeProto) { + FlashModeProto.FLASH_MODE_AUTO -> FlashMode.AUTO + FlashModeProto.FLASH_MODE_ON -> FlashMode.ON + FlashModeProto.FLASH_MODE_OFF -> FlashMode.OFF + FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST -> FlashMode.LOW_LIGHT_BOOST + else -> FlashMode.OFF // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun streamConfig_convertsToCorrectProto() { + val correctConversions = { streamConfig: StreamConfig -> + when (streamConfig) { + StreamConfig.MULTI_STREAM -> StreamConfigProto.STREAM_CONFIG_MULTI_STREAM + StreamConfig.SINGLE_STREAM -> StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun streamConfigProto_convertsToCorrectStreamConfig() { + val correctConversions = { streamConfigProto: StreamConfigProto -> + when (streamConfigProto) { + StreamConfigProto.STREAM_CONFIG_MULTI_STREAM -> StreamConfig.MULTI_STREAM + StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM -> StreamConfig.SINGLE_STREAM + else -> StreamConfig.MULTI_STREAM // Default value + } + } + + enumValues().forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } + + @Test + fun debugSettings_convertsToCorrectProto() { + val correctConversions = { debugSettings: DebugSettings -> + debugSettingsProto { + isDebugModeEnabled = debugSettings.isDebugModeEnabled + debugSettings.singleLensMode?.let { + singleLensMode = it.toProto() + } + testPattern = debugSettings.testPattern.toProto() + } + } + + val debugSettingsToTest = listOf( + DebugSettings(), + DebugSettings(isDebugModeEnabled = true), + DebugSettings(singleLensMode = LensFacing.FRONT), + DebugSettings(testPattern = TestPattern.ColorBars), + DebugSettings( + isDebugModeEnabled = true, + singleLensMode = LensFacing.BACK, + testPattern = TestPattern.PN9 + ) + ) + + debugSettingsToTest.forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun debugSettingsProto_convertsToCorrectDebugSettings() { + val correctConversions = { debugSettingsProto: DebugSettingsProto -> + DebugSettings( + isDebugModeEnabled = debugSettingsProto.isDebugModeEnabled, + singleLensMode = if (debugSettingsProto.hasSingleLensMode()) { + debugSettingsProto.singleLensMode.toDomain() + } else { + null + }, + testPattern = debugSettingsProto.testPattern.toDomain() + ) + } + + val debugSettingsProtosToTest: List = listOf( + debugSettingsProto { }, + debugSettingsProto { isDebugModeEnabled = true }, + debugSettingsProto { singleLensMode = LensFacingProto.LENS_FACING_FRONT }, + debugSettingsProto { + testPattern = protoTestPattern { + colorBars = testPatternColorBars { } + } + }, + debugSettingsProto { + isDebugModeEnabled = true + singleLensMode = LensFacingProto.LENS_FACING_BACK + testPattern = protoTestPattern { + pn9 = testPatternPN9 { } + } + } + ) + + debugSettingsProtosToTest.forEach { + Truth.assertThat(correctConversions(it)).isEqualTo(it.toDomain()) + } + } +} diff --git a/data/settings-datastore/src/androidTest/java/com/google/jetpackcamera/data/settings_datastore/LocalSettingsRepositoryInstrumentedTest.kt b/data/settings-datastore/src/androidTest/java/com/google/jetpackcamera/data/settings_datastore/LocalSettingsRepositoryInstrumentedTest.kt index 65a6146eb..c984ec1ac 100644 --- a/data/settings-datastore/src/androidTest/java/com/google/jetpackcamera/data/settings_datastore/LocalSettingsRepositoryInstrumentedTest.kt +++ b/data/settings-datastore/src/androidTest/java/com/google/jetpackcamera/data/settings_datastore/LocalSettingsRepositoryInstrumentedTest.kt @@ -22,6 +22,7 @@ import androidx.datastore.dataStoreFile import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import com.google.jetpackcamera.data.settingsdatastore.JcaSettingsSerializer import com.google.jetpackcamera.data.settingsdatastore.LocalSettingsRepository import com.google.jetpackcamera.model.CaptureMode import com.google.jetpackcamera.model.DarkMode diff --git a/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt b/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt index 376a16089..939068567 100644 --- a/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt +++ b/data/settings-datastore/src/main/java/com/google/jetpackcamera/data/settingsdatastore/LocalSettingsRepository.kt @@ -21,30 +21,23 @@ import com.google.jetpackcamera.model.AspectRatio import com.google.jetpackcamera.model.CaptureMode import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DynamicRange -import com.google.jetpackcamera.model.DynamicRange.Companion.toProto import com.google.jetpackcamera.model.FlashMode import com.google.jetpackcamera.model.ImageOutputFormat -import com.google.jetpackcamera.model.ImageOutputFormat.Companion.toProto import com.google.jetpackcamera.model.LensFacing -import com.google.jetpackcamera.model.LensFacing.Companion.toProto import com.google.jetpackcamera.model.LowLightBoostPriority -import com.google.jetpackcamera.model.LowLightBoostPriority.Companion.fromProto -import com.google.jetpackcamera.model.LowLightBoostPriority.Companion.toProto import com.google.jetpackcamera.model.StabilizationMode import com.google.jetpackcamera.model.StreamConfig import com.google.jetpackcamera.model.VideoQuality -import com.google.jetpackcamera.model.VideoQuality.Companion.toProto -import com.google.jetpackcamera.model.proto.AspectRatio as AspectRatioProto +import com.google.jetpackcamera.model.mappers.toDomain +import com.google.jetpackcamera.model.mappers.toProto import com.google.jetpackcamera.model.proto.DarkMode as DarkModeProto -import com.google.jetpackcamera.model.proto.FlashMode as FlashModeProto -import com.google.jetpackcamera.model.proto.StabilizationMode as StabilizationModeProto -import com.google.jetpackcamera.model.proto.StreamConfig as StreamConfigProto import com.google.jetpackcamera.settings.JcaSettings import com.google.jetpackcamera.settings.SettingsRepository import com.google.jetpackcamera.settings.model.CameraAppSettings import javax.inject.Inject import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map + /** * Implementation of [SettingsRepository] with locally stored settings. */ @@ -57,33 +50,23 @@ class LocalSettingsRepository @Inject constructor( override val defaultCameraAppSettings = jcaSettings.data .map { CameraAppSettings( - cameraLensFacing = LensFacing.fromProto(it.defaultLensFacing), + cameraLensFacing = it.defaultLensFacing.toDomain(), darkMode = when (it.darkModeStatus) { DarkModeProto.DARK_MODE_DARK -> DarkMode.DARK DarkModeProto.DARK_MODE_LIGHT -> DarkMode.LIGHT DarkModeProto.DARK_MODE_SYSTEM -> DarkMode.SYSTEM else -> DarkMode.DARK }, - flashMode = when (it.flashModeStatus) { - FlashModeProto.FLASH_MODE_AUTO -> FlashMode.AUTO - FlashModeProto.FLASH_MODE_ON -> FlashMode.ON - FlashModeProto.FLASH_MODE_OFF -> FlashMode.OFF - FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST -> FlashMode.LOW_LIGHT_BOOST - else -> FlashMode.OFF - }, - aspectRatio = AspectRatio.fromProto(it.aspectRatioStatus), - stabilizationMode = StabilizationMode.fromProto(it.stabilizationMode), + flashMode = it.flashModeStatus.toDomain(), + aspectRatio = it.aspectRatioStatus.toDomain(), + stabilizationMode = it.stabilizationMode.toDomain(), targetFrameRate = it.targetFrameRate, - streamConfig = when (it.streamConfigStatus) { - StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM -> StreamConfig.SINGLE_STREAM - StreamConfigProto.STREAM_CONFIG_MULTI_STREAM -> StreamConfig.MULTI_STREAM - else -> StreamConfig.MULTI_STREAM - }, - lowLightBoostPriority = fromProto(it.lowLightBoostPriority), - dynamicRange = DynamicRange.fromProto(it.dynamicRangeStatus), - imageFormat = ImageOutputFormat.fromProto(it.imageFormatStatus), + streamConfig = it.streamConfigStatus.toDomain(), + lowLightBoostPriority = it.lowLightBoostPriority.toDomain(), + dynamicRange = it.dynamicRangeStatus.toDomain(), + imageFormat = it.imageFormatStatus.toDomain(), maxVideoDurationMillis = it.maxVideoDurationMillis, - videoQuality = VideoQuality.fromProto(it.videoQuality), + videoQuality = it.videoQuality.toDomain(), audioEnabled = it.audioEnabledStatus, captureMode = defaultCaptureModeOverride ) @@ -114,15 +97,9 @@ class LocalSettingsRepository @Inject constructor( } override suspend fun updateFlashModeStatus(flashMode: FlashMode) { - val newStatus = when (flashMode) { - FlashMode.AUTO -> FlashModeProto.FLASH_MODE_AUTO - FlashMode.ON -> FlashModeProto.FLASH_MODE_ON - FlashMode.OFF -> FlashModeProto.FLASH_MODE_OFF - FlashMode.LOW_LIGHT_BOOST -> FlashModeProto.FLASH_MODE_LOW_LIGHT_BOOST - } jcaSettings.updateData { currentSettings -> currentSettings.toBuilder() - .setFlashModeStatus(newStatus) + .setFlashModeStatus(flashMode.toProto()) .build() } } @@ -136,38 +113,23 @@ class LocalSettingsRepository @Inject constructor( } override suspend fun updateAspectRatio(aspectRatio: AspectRatio) { - val newStatus = when (aspectRatio) { - AspectRatio.NINE_SIXTEEN -> AspectRatioProto.ASPECT_RATIO_NINE_SIXTEEN - AspectRatio.THREE_FOUR -> AspectRatioProto.ASPECT_RATIO_THREE_FOUR - AspectRatio.ONE_ONE -> AspectRatioProto.ASPECT_RATIO_ONE_ONE - } jcaSettings.updateData { currentSettings -> currentSettings.toBuilder() - .setAspectRatioStatus(newStatus) + .setAspectRatioStatus(aspectRatio.toProto()) .build() } } override suspend fun updateStreamConfig(streamConfig: StreamConfig) { - val newStatus = when (streamConfig) { - StreamConfig.MULTI_STREAM -> StreamConfigProto.STREAM_CONFIG_MULTI_STREAM - StreamConfig.SINGLE_STREAM -> StreamConfigProto.STREAM_CONFIG_SINGLE_STREAM - } jcaSettings.updateData { currentSettings -> currentSettings.toBuilder() - .setStreamConfigStatus(newStatus) + .setStreamConfigStatus(streamConfig.toProto()) .build() } } override suspend fun updateStabilizationMode(stabilizationMode: StabilizationMode) { - val newStatus = when (stabilizationMode) { - StabilizationMode.OFF -> StabilizationModeProto.STABILIZATION_MODE_OFF - StabilizationMode.AUTO -> StabilizationModeProto.STABILIZATION_MODE_AUTO - StabilizationMode.ON -> StabilizationModeProto.STABILIZATION_MODE_ON - StabilizationMode.HIGH_QUALITY -> StabilizationModeProto.STABILIZATION_MODE_HIGH_QUALITY - StabilizationMode.OPTICAL -> StabilizationModeProto.STABILIZATION_MODE_OPTICAL - } + val newStatus = stabilizationMode.toProto() jcaSettings.updateData { currentSettings -> currentSettings.toBuilder() .setStabilizationMode(newStatus) @@ -190,6 +152,7 @@ class LocalSettingsRepository @Inject constructor( .build() } } + override suspend fun updateMaxVideoDuration(durationMillis: Long) { jcaSettings.updateData { currentSettings -> currentSettings.toBuilder() @@ -221,4 +184,4 @@ class LocalSettingsRepository @Inject constructor( .build() } } -} \ No newline at end of file +} diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/DebugSettingsNavType.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/DebugSettingsNavType.kt index d2c4c5dfa..dd83d8ea7 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/DebugSettingsNavType.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/DebugSettingsNavType.kt @@ -18,8 +18,10 @@ package com.google.jetpackcamera.feature.preview.navigation import android.os.Bundle import androidx.navigation.NavType import com.google.jetpackcamera.model.DebugSettings -import com.google.jetpackcamera.model.DebugSettings.Companion.encodeAsByteArray -import com.google.jetpackcamera.model.DebugSettings.Companion.encodeAsString +import com.google.jetpackcamera.model.mappers.DebugSettingsMapper.encodeAsByteArray +import com.google.jetpackcamera.model.mappers.DebugSettingsMapper.encodeAsString +import com.google.jetpackcamera.model.mappers.DebugSettingsMapper.parseFromByteArray +import com.google.jetpackcamera.model.mappers.DebugSettingsMapper.parseFromString /** * Custom NavType to handle DebugSettings data class. @@ -39,14 +41,14 @@ internal object DebugSettingsNavType : NavType(isNullableAllowed */ override fun get(bundle: Bundle, key: String): DebugSettings? { return bundle.getByteArray(key)?.let { bytes -> - DebugSettings.parseFromByteArray(bytes) + parseFromByteArray(bytes) } } /** * Parses the Base64 encoded Proto string from the navigation route. */ - override fun parseValue(value: String): DebugSettings = DebugSettings.parseFromString(value) + override fun parseValue(value: String): DebugSettings = parseFromString(value) /** * Encodes the [DebugSettings] data class to a Base64 string for navigation routes. diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/PreviewNavigation.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/PreviewNavigation.kt index 60b91ba52..080cd6755 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/PreviewNavigation.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/navigation/PreviewNavigation.kt @@ -40,6 +40,7 @@ import com.google.jetpackcamera.model.CaptureEvent import com.google.jetpackcamera.model.DebugSettings import com.google.jetpackcamera.model.ExternalCaptureMode import com.google.jetpackcamera.model.SaveMode +import com.google.jetpackcamera.model.mappers.DebugSettingsMapper object PreviewRoute { internal const val ARG_EXTERNAL_CAPTURE_MODE: String = "externalCaptureMode" @@ -188,5 +189,5 @@ internal fun SavedStateHandle.getCaptureUris(defaultIfMissing: List = empty internal fun SavedStateHandle.getDebugSettings( defaultIfMissing: DebugSettings = DebugSettings() -): DebugSettings = get(ARG_DEBUG_SETTINGS)?.let(DebugSettings::parseFromByteArray) +): DebugSettings = get(ARG_DEBUG_SETTINGS)?.let(DebugSettingsMapper::parseFromByteArray) ?: defaultIfMissing