Skip to content

Commit

Permalink
Add HDR settings to model (#111)
Browse files Browse the repository at this point in the history
* Convert settings tests to use Truth

* Add dynamic range proto and setting

 Only includes SDR and HLG10 currently

* Add supported dynamic ranges to settings

* Apply spotless

* Make copyright header years reflect date of inception
  • Loading branch information
temcguir authored Feb 20, 2024
1 parent 6e32e86 commit 8498fe2
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 44 deletions.
14 changes: 9 additions & 5 deletions data/settings/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,23 @@ android {
}

dependencies {
// Testing
testImplementation(libs.junit)
implementation(libs.kotlinx.coroutines.core)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

// Hilt
implementation(libs.dagger.hilt.android)
kapt(libs.dagger.hilt.compiler)

// proto datastore
implementation(libs.androidx.datastore)
implementation(libs.protobuf.kotlin.lite)

// Testing
testImplementation(libs.junit)
testImplementation(libs.truth)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.truth)
androidTestImplementation(libs.kotlinx.coroutines.test)
}

protobuf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ package com.google.jetpackcamera.settings

import androidx.datastore.core.DataStore
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.google.jetpackcamera.settings.test.FakeDataStoreModule
import com.google.jetpackcamera.settings.test.FakeJcaSettingsSerializer
import java.io.File
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
Expand Down Expand Up @@ -53,6 +53,6 @@ class DataStoreModuleTest {
val datastoreValue = dataStore.data.first()
advanceUntilIdle()

assertEquals(datastoreValue, JcaSettings.getDefaultInstance())
assertThat(datastoreValue).isEqualTo(JcaSettings.getDefaultInstance())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import androidx.datastore.core.DataStoreFactory
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.settings.DataStoreModule.provideDataStore
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
import java.io.File
import kotlinx.coroutines.CoroutineScope
Expand All @@ -37,9 +39,6 @@ import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -59,7 +58,7 @@ class LocalSettingsRepositoryInstrumentedTest {
private lateinit var repository: LocalSettingsRepository

@Before
fun setup() = runTest(StandardTestDispatcher()) {
fun setup() = runTest {
Dispatchers.setMain(StandardTestDispatcher())
testDataStore = provideDataStore(testContext)
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
Expand All @@ -85,56 +84,56 @@ class LocalSettingsRepositoryInstrumentedTest {
}

@Test
fun repository_can_fetch_initial_datastore() = runTest(StandardTestDispatcher()) {
fun repository_can_fetch_initial_datastore() = runTest {
// if you've created a new setting value and this test is failing, be sure to check that
// JcaSettingsSerializer.kt defaultValue has been properly modified :)

val cameraAppSettings: CameraAppSettings = repository.getCameraAppSettings()

advanceUntilIdle()
assertTrue(cameraAppSettings == DEFAULT_CAMERA_APP_SETTINGS)
assertThat(cameraAppSettings).isEqualTo(DEFAULT_CAMERA_APP_SETTINGS)
}

@Test
fun can_update_dark_mode() = runTest(StandardTestDispatcher()) {
fun can_update_dark_mode() = runTest {
val initialDarkModeStatus = repository.getCameraAppSettings().darkMode
repository.updateDarkModeStatus(DarkMode.LIGHT)
val newDarkModeStatus = repository.getCameraAppSettings().darkMode

advanceUntilIdle()
assertFalse(initialDarkModeStatus == newDarkModeStatus)
assertTrue(initialDarkModeStatus == DarkMode.SYSTEM)
assertTrue(newDarkModeStatus == DarkMode.LIGHT)
assertThat(initialDarkModeStatus).isNotEqualTo(newDarkModeStatus)
assertThat(initialDarkModeStatus).isEqualTo(DarkMode.SYSTEM)
assertThat(newDarkModeStatus).isEqualTo(DarkMode.LIGHT)
}

@Test
fun can_update_default_to_front_camera() = runTest(StandardTestDispatcher()) {
fun can_update_default_to_front_camera() = runTest {
// default to front camera starts false
val initialFrontCameraDefault = repository.getCameraAppSettings().isFrontCameraFacing
repository.updateDefaultToFrontCamera()
// default to front camera is now true
val frontCameraDefault = repository.getCameraAppSettings().isFrontCameraFacing
advanceUntilIdle()

assertFalse(initialFrontCameraDefault)
assertTrue(frontCameraDefault)
assertThat(initialFrontCameraDefault).isFalse()
assertThat(frontCameraDefault).isTrue()
}

@Test
fun can_update_flash_mode() = runTest(StandardTestDispatcher()) {
fun can_update_flash_mode() = runTest {
// default to front camera starts false
val initialFlashModeStatus = repository.getCameraAppSettings().flashMode
repository.updateFlashModeStatus(FlashMode.ON)
// default to front camera is now true
val newFlashModeStatus = repository.getCameraAppSettings().flashMode
advanceUntilIdle()

assertEquals(initialFlashModeStatus, FlashMode.OFF)
assertEquals(newFlashModeStatus, FlashMode.ON)
assertThat(initialFlashModeStatus).isEqualTo(FlashMode.OFF)
assertThat(newFlashModeStatus).isEqualTo(FlashMode.ON)
}

@Test
fun can_update_available_camera_lens() = runTest(StandardTestDispatcher()) {
fun can_update_available_camera_lens() = runTest {
// available cameras start true
val initialFrontCamera = repository.getCameraAppSettings().isFrontCameraAvailable
val initialBackCamera = repository.getCameraAppSettings().isBackCameraAvailable
Expand All @@ -145,7 +144,37 @@ class LocalSettingsRepositoryInstrumentedTest {
val newFrontCamera = repository.getCameraAppSettings().isFrontCameraAvailable
val newBackCamera = repository.getCameraAppSettings().isBackCameraAvailable

assertEquals(true, initialFrontCamera && initialBackCamera)
assertEquals(false, newFrontCamera || newBackCamera)
assertThat(initialFrontCamera && initialBackCamera).isTrue()
assertThat(newFrontCamera || newBackCamera).isFalse()
}

@Test
fun can_update_dynamic_range() = runTest {
val initialDynamicRange = repository.getCameraAppSettings().dynamicRange

repository.updateDynamicRange(dynamicRange = DynamicRange.HLG10)

advanceUntilIdle()

val newDynamicRange = repository.getCameraAppSettings().dynamicRange

assertThat(initialDynamicRange).isEqualTo(DynamicRange.SDR)
assertThat(newDynamicRange).isEqualTo(DynamicRange.HLG10)
}

@Test
fun can_update_supported_dynamic_ranges() = runTest {
val initialSupportedDynamicRanges = repository.getCameraAppSettings().supportedDynamicRanges

repository.updateSupportedDynamicRanges(
supportedDynamicRanges = listOf(DynamicRange.SDR, DynamicRange.HLG10)
)

advanceUntilIdle()

val newSupportedDynamicRanges = repository.getCameraAppSettings().supportedDynamicRanges

assertThat(initialSupportedDynamicRanges).containsExactly(DynamicRange.SDR)
assertThat(newSupportedDynamicRanges).containsExactly(DynamicRange.SDR, DynamicRange.HLG10)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ object JcaSettingsSerializer : Serializer<JcaSettings> {
.setStabilizeVideo(VideoStabilization.VIDEO_STABILIZATION_UNDEFINED)
.setStabilizePreviewSupported(false)
.setStabilizeVideoSupported(false)
.setDynamicRangeStatus(DynamicRange.DYNAMIC_RANGE_UNSPECIFIED)
.addSupportedDynamicRanges(DynamicRange.DYNAMIC_RANGE_SDR)
.build()

override suspend fun readFrom(input: InputStream): JcaSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.DynamicRange.Companion.toProto
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
Expand Down Expand Up @@ -69,7 +71,12 @@ class LocalSettingsRepository @Inject constructor(
CaptureModeProto.CAPTURE_MODE_SINGLE_STREAM -> CaptureMode.SINGLE_STREAM
CaptureModeProto.CAPTURE_MODE_MULTI_STREAM -> CaptureMode.MULTI_STREAM
else -> CaptureMode.MULTI_STREAM
},
dynamicRange = DynamicRange.fromProto(it.dynamicRangeStatus),
supportedDynamicRanges = it.supportedDynamicRangesList.map { dynRngProto ->
DynamicRange.fromProto(dynRngProto)
}

)
}

Expand Down Expand Up @@ -209,4 +216,25 @@ class LocalSettingsRepository @Inject constructor(
}
}
}

override suspend fun updateDynamicRange(dynamicRange: DynamicRange) {
jcaSettings.updateData { currentSettings ->
currentSettings.toBuilder()
.setDynamicRangeStatus(dynamicRange.toProto())
.build()
}
}

override suspend fun updateSupportedDynamicRanges(supportedDynamicRanges: List<DynamicRange>) {
jcaSettings.updateData { currentSettings ->
currentSettings.toBuilder()
.clearSupportedDynamicRanges()
.addAllSupportedDynamicRanges(
supportedDynamicRanges.map {
it.toProto()
}
)
.build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -49,6 +50,9 @@ interface SettingsRepository {
suspend fun updateVideoStabilizationSupported(isSupported: Boolean)

suspend fun updatePreviewStabilizationSupported(isSupported: Boolean)
suspend fun updateDynamicRange(dynamicRange: DynamicRange)

suspend fun updateSupportedDynamicRanges(supportedDynamicRanges: List<DynamicRange>)

suspend fun getCameraAppSettings(): CameraAppSettings
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ data class CameraAppSettings(
val aspectRatio: AspectRatio = AspectRatio.NINE_SIXTEEN,
val previewStabilization: Stabilization = Stabilization.UNDEFINED,
val videoCaptureStabilization: Stabilization = Stabilization.UNDEFINED,
val supportedStabilizationModes: List<SupportedStabilizationMode> = emptyList()
val supportedStabilizationModes: List<SupportedStabilizationMode> = emptyList(),
val dynamicRange: DynamicRange = DynamicRange.SDR,
val supportedDynamicRanges: List<DynamicRange> = listOf(DynamicRange.SDR)
)

val DEFAULT_CAMERA_APP_SETTINGS = CameraAppSettings()
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 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.model

import com.google.jetpackcamera.settings.DynamicRange as DynamicRangeProto

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
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
Expand Down Expand Up @@ -101,6 +102,16 @@ object FakeSettingsRepository : SettingsRepository {
currentCameraSettings.copy(supportedStabilizationModes = stabilizationModes)
}

override suspend fun updateDynamicRange(dynamicRange: DynamicRange) {
currentCameraSettings =
currentCameraSettings.copy(dynamicRange = dynamicRange)
}

override suspend fun updateSupportedDynamicRanges(supportedDynamicRanges: List<DynamicRange>) {
currentCameraSettings =
currentCameraSettings.copy(supportedDynamicRanges = supportedDynamicRanges)
}

override suspend fun updateAspectRatio(aspectRatio: AspectRatio) {
currentCameraSettings =
currentCameraSettings.copy(aspectRatio = aspectRatio)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 The Android Open Source Project
* Copyright (C) 2024 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.
Expand All @@ -13,19 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.jetpackcamera.settings

import org.junit.Assert.assertEquals
import org.junit.Test
syntax = "proto3";

/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
option java_package = "com.google.jetpackcamera.settings";
option java_multiple_files = true;

enum DynamicRange {
DYNAMIC_RANGE_UNSPECIFIED = 0;
DYNAMIC_RANGE_SDR = 1;
DYNAMIC_RANGE_HLG10 = 2;
}
Loading

0 comments on commit 8498fe2

Please sign in to comment.