Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.jetpackcamera

import android.os.Build
import androidx.compose.ui.test.isNotSelected
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
Expand All @@ -27,14 +28,18 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
import com.google.jetpackcamera.settings.ui.BTN_DIALOG_STREAM_CONFIG_OPTION_MULTI_STREAM_CAPTURE_TAG
import com.google.jetpackcamera.settings.ui.BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG
import com.google.jetpackcamera.settings.ui.BTN_OPEN_DIALOG_SETTING_STREAM_CONFIG_TAG
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_DROP_DOWN
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_FLIP_CAMERA_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_1_1_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_STREAM_CONFIG_BUTTON
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.runMainActivityScenarioTest
import com.google.jetpackcamera.utils.visitSettingDialog
import com.google.jetpackcamera.utils.visitSettingsScreen
import com.google.jetpackcamera.utils.waitForCaptureButton
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -135,27 +140,30 @@ class BackgroundDeviceTest {
}

@Test
fun toggleCaptureMode_then_background_foreground() = runMainActivityScenarioTest {
fun toggleStreamConfig_then_background_foreground() = runMainActivityScenarioTest {
// Skip this test on devices that don't support single stream
assumeSupportsSingleStream()

// Wait for the capture button to be displayed
composeTestRule.waitForCaptureButton()

// Navigate to quick settings
composeTestRule.onNodeWithTag(QUICK_SETTINGS_DROP_DOWN)
.assertExists()
.performClick()

// Click the flip camera button
composeTestRule.onNodeWithTag(QUICK_SETTINGS_STREAM_CONFIG_BUTTON)
.assertExists()
.performClick()

// Exit quick settings
composeTestRule.onNodeWithTag(QUICK_SETTINGS_DROP_DOWN)
.assertExists()
.performClick()
composeTestRule.visitSettingsScreen {
visitSettingDialog(
settingTestTag = BTN_OPEN_DIALOG_SETTING_STREAM_CONFIG_TAG,
dialogTestTag = BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG,
disabledMessage = "Stream configuration component is disabled"
) {
val singleStreamNode =
onNodeWithTag(BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG)
if (isNotSelected().matches(singleStreamNode.fetchSemanticsNode())) {
singleStreamNode.performClick()
} else {
onNodeWithTag(
BTN_DIALOG_STREAM_CONFIG_OPTION_MULTI_STREAM_CAPTURE_TAG
).performClick()
}
}
}

backgroundThenForegroundApp()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_FLIP_CAMERA
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_HDR_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_1_1_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_BUTTON
import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_STREAM_CONFIG_BUTTON
import com.google.jetpackcamera.ui.components.capture.R
import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
Expand Down Expand Up @@ -236,11 +235,6 @@ class ConcurrentCameraTest {
.performClick()
.assertConcurrentCameraMode(ConcurrentCameraMode.DUAL)

// Assert the capture mode button is disabled
onNodeWithTag(QUICK_SETTINGS_STREAM_CONFIG_BUTTON)
.assertExists()
.assert(isNotEnabled())

// Assert the HDR button is disabled
onNodeWithTag(QUICK_SETTINGS_HDR_BUTTON)
.assertExists()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.google.common.truth.Truth.assertThat
import com.google.jetpackcamera.model.CaptureMode
import com.google.jetpackcamera.model.DarkMode
import com.google.jetpackcamera.model.LensFacing
import com.google.jetpackcamera.model.proto.ImageOutputFormat as ImageOutputFormatProto
import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
import java.io.File
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -194,6 +195,30 @@ internal class CameraAppSettingsViewModelTest {
assertThat(initialDarkMode).isEqualTo(DarkMode.DARK)
assertThat(newDarkMode).isEqualTo(DarkMode.SYSTEM)
}

@Test
fun streamConfigDisabled_whenUltraHdrEnabled() = runTest(StandardTestDispatcher()) {
// Set image format to Ultra HDR in datastore
testDataStore.updateData { currentSettings ->
currentSettings.toBuilder()
.setImageFormatStatus(
ImageOutputFormatProto.IMAGE_OUTPUT_FORMAT_JPEG_ULTRA_HDR
)
.build()
}
advanceUntilIdle()

val uiState = settingsViewModel.settingsUiState.first {
it is SettingsUiState.Enabled
}

val streamConfigUiState = assertIsEnabled(uiState).streamConfigUiState
assertThat(streamConfigUiState).isInstanceOf(StreamConfigUiState.Disabled::class.java)
val disabledRationale =
(streamConfigUiState as StreamConfigUiState.Disabled).disabledRationale
assertThat(disabledRationale)
.isInstanceOf(DisabledRationale.UltraHdrUnsupportedRationale::class.java)
}
}

private fun assertIsEnabled(settingsUiState: SettingsUiState): SettingsUiState.Enabled =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import com.google.jetpackcamera.model.VideoQuality
import com.google.jetpackcamera.settings.DisabledRationale.DeviceUnsupportedRationale
import com.google.jetpackcamera.settings.DisabledRationale.LensUnsupportedRationale
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.ui.CONCURRENT_CAMERA_ENABLED_TAG
import com.google.jetpackcamera.settings.ui.DEVICE_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.FPS_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.LENS_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.PERMISSION_RECORD_AUDIO_NOT_GRANTED_TAG
import com.google.jetpackcamera.settings.ui.STABILIZATION_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.ULTRA_HDR_ENABLED_TAG
import com.google.jetpackcamera.settings.ui.VIDEO_QUALITY_UNSUPPORTED_TAG
internal const val FIVE_SECONDS_DURATION = 5_000L
internal const val TEN_SECONDS_DURATION = 10_000L
Expand Down Expand Up @@ -109,6 +111,18 @@ sealed interface DisabledRationale {
override val testTag = VIDEO_QUALITY_UNSUPPORTED_TAG
}

data class ConcurrentCameraUnsupportedRationale(override val affectedSettingNameResId: Int) :
DisabledRationale {
override val reasonTextResId: Int = R.string.concurrent_camera_enabled
override val testTag = CONCURRENT_CAMERA_ENABLED_TAG
}

data class UltraHdrUnsupportedRationale(override val affectedSettingNameResId: Int) :
DisabledRationale {
override val reasonTextResId: Int = R.string.ultra_hdr_enabled
override val testTag = ULTRA_HDR_ENABLED_TAG
}

sealed interface LensUnsupportedRationale : DisabledRationale {
data class FrontLensUnsupportedRationale(override val affectedSettingNameResId: Int) :
LensUnsupportedRationale {
Expand Down Expand Up @@ -231,6 +245,8 @@ sealed interface AspectRatioUiState {
sealed interface StreamConfigUiState {
data class Enabled(val currentStreamConfig: StreamConfig, val additionalContext: String = "") :
StreamConfigUiState

data class Disabled(val disabledRationale: DisabledRationale) : StreamConfigUiState
}

sealed interface DarkModeUiState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.isGranted
import com.google.jetpackcamera.model.AspectRatio
import com.google.jetpackcamera.model.ConcurrentCameraMode
import com.google.jetpackcamera.model.DarkMode
import com.google.jetpackcamera.model.DynamicRange
import com.google.jetpackcamera.model.FlashMode
import com.google.jetpackcamera.model.ImageOutputFormat
import com.google.jetpackcamera.model.LensFacing
import com.google.jetpackcamera.model.LowLightBoostPriority
import com.google.jetpackcamera.model.StabilizationMode
Expand Down Expand Up @@ -76,7 +78,7 @@ class SettingsViewModel @Inject constructor(
updatedSettings.videoQuality
SettingsUiState.Enabled(
aspectRatioUiState = AspectRatioUiState.Enabled(updatedSettings.aspectRatio),
streamConfigUiState = StreamConfigUiState.Enabled(updatedSettings.streamConfig),
streamConfigUiState = getStreamConfigUiState(updatedSettings),
maxVideoDurationUiState = MaxVideoDurationUiState.Enabled(
updatedSettings.maxVideoDurationMillis
),
Expand Down Expand Up @@ -193,6 +195,26 @@ class SettingsViewModel @Inject constructor(
)
}

private fun getStreamConfigUiState(cameraAppSettings: CameraAppSettings): StreamConfigUiState {
if (cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.DUAL) {
return StreamConfigUiState.Disabled(
DisabledRationale.ConcurrentCameraUnsupportedRationale(
R.string.stream_config_rationale_prefix
)
)
}

if (cameraAppSettings.imageFormat == ImageOutputFormat.JPEG_ULTRA_HDR) {
return StreamConfigUiState.Disabled(
DisabledRationale.UltraHdrUnsupportedRationale(
R.string.stream_config_rationale_prefix
)
)
}

return StreamConfigUiState.Enabled(cameraAppSettings.streamConfig)
}

private fun getAudioUiState(isAudioEnabled: Boolean, permissionGranted: Boolean): AudioUiState =
if (permissionGranted) {
if (isAudioEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,40 +357,51 @@ fun StreamConfigSetting(
modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_STREAM_CONFIG_TAG),
title = stringResource(R.string.stream_config_title),
leadingIcon = null,
enabled = true,
enabled = streamConfigUiState is StreamConfigUiState.Enabled,
description =
if (streamConfigUiState is StreamConfigUiState.Enabled) {
when (streamConfigUiState.currentStreamConfig) {
StreamConfig.MULTI_STREAM -> stringResource(
id = R.string.stream_config_description_multi_stream
)
when (streamConfigUiState) {
is StreamConfigUiState.Enabled -> {
when (streamConfigUiState.currentStreamConfig) {
StreamConfig.MULTI_STREAM -> stringResource(
id = R.string.stream_config_description_multi_stream
)

StreamConfig.SINGLE_STREAM -> stringResource(
id = R.string.stream_config_description_single_stream
)
StreamConfig.SINGLE_STREAM -> stringResource(
id = R.string.stream_config_description_single_stream
)
}
}

is StreamConfigUiState.Disabled -> {
disabledRationaleString(disabledRationale = streamConfigUiState.disabledRationale)
}
} else {
TODO("stream config currently has no disabled criteria")
},
popupContents = {
Column(Modifier.selectableGroup()) {
SingleChoiceSelector(
modifier = Modifier.testTag(
BTN_DIALOG_STREAM_CONFIG_OPTION_MULTI_STREAM_CAPTURE_TAG
),
text = stringResource(id = R.string.stream_config_selector_multi_stream),
selected = streamConfigUiState.currentStreamConfig == StreamConfig.MULTI_STREAM,
enabled = true,
onClick = { setStreamConfig(StreamConfig.MULTI_STREAM) }
)
SingleChoiceSelector(
modifier = Modifier.testTag(BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG),
text = stringResource(id = R.string.stream_config_description_single_stream),
selected = streamConfigUiState.currentStreamConfig ==
StreamConfig.SINGLE_STREAM,
enabled = true,
onClick = { setStreamConfig(StreamConfig.SINGLE_STREAM) }
)
if (streamConfigUiState is StreamConfigUiState.Enabled) {
Column(Modifier.selectableGroup()) {
SingleChoiceSelector(
modifier = Modifier.testTag(
BTN_DIALOG_STREAM_CONFIG_OPTION_MULTI_STREAM_CAPTURE_TAG
),
text = stringResource(id = R.string.stream_config_selector_multi_stream),
selected = streamConfigUiState.currentStreamConfig ==
StreamConfig.MULTI_STREAM,
enabled = true,
onClick = { setStreamConfig(StreamConfig.MULTI_STREAM) }
)
SingleChoiceSelector(
modifier = Modifier.testTag(
BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG
),
text = stringResource(
id = R.string.stream_config_description_single_stream
),
selected = streamConfigUiState.currentStreamConfig ==
StreamConfig.SINGLE_STREAM,
enabled = true,
onClick = { setStreamConfig(StreamConfig.SINGLE_STREAM) }
)
}
}
}
)
Expand Down Expand Up @@ -1094,6 +1105,16 @@ fun disabledRationaleString(disabledRationale: DisabledRationale): String =
disabledRationale.reasonTextResId,
stringResource(disabledRationale.affectedSettingNameResId)
)

is DisabledRationale.ConcurrentCameraUnsupportedRationale -> stringResource(
disabledRationale.reasonTextResId,
stringResource(disabledRationale.affectedSettingNameResId)
)

is DisabledRationale.UltraHdrUnsupportedRationale -> stringResource(
disabledRationale.reasonTextResId,
stringResource(disabledRationale.affectedSettingNameResId)
)
}

@Preview(name = "Light Mode")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const val LENS_UNSUPPORTED_TAG = "LensUnsupportedTag"
const val FPS_UNSUPPORTED_TAG = "FpsUnsupportedTag"
const val VIDEO_QUALITY_UNSUPPORTED_TAG = "VideoQualityUnsupportedTag"
const val PERMISSION_RECORD_AUDIO_NOT_GRANTED_TAG = "PermissionRecordAudioNotGrantedTag"
const val CONCURRENT_CAMERA_ENABLED_TAG = "ConcurrentCameraEnabledTag"
const val ULTRA_HDR_ENABLED_TAG = "UltraHdrEnabledTag"

// Settings w/ no dialog
const val BTN_SWITCH_SETTING_LENS_FACING_TAG = "btn_switch_setting_lens_facing_tag"
Expand Down
4 changes: 4 additions & 0 deletions feature/settings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
<string name="video_quality_unsupported">%1$s is unsupported by the current video quality</string>
<string name="permission_record_audio_unsupported">%1$s requires permission to record audio</string>

<string name="concurrent_camera_enabled">%1$s is unsupported when Concurrent Camera is active</string>
<string name="ultra_hdr_enabled">%1$s is unsupported when Ultra HDR is enabled</string>


<!-- Rationale prefixes -->
<string name="stabilization_rationale_prefix">Stabilization</string>
Expand All @@ -178,6 +181,7 @@
<string name="no_fixed_fps_rationale_prefix">Fixed frame rate</string>

<string name="mute_audio_rationale_prefix">Mute</string>
<string name="stream_config_rationale_prefix">Stream configuration</string>

<!-- Version info strings -->
<string name="version_info_title">Version</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ const val QUICK_SETTINGS_BOTTOM_SHEET = "QuickSettingsBottomSheet"
const val QUICK_SETTINGS_CLOSE_EXPANDED_BUTTON = "QuickSettingsCloseExpandedButton"
const val QUICK_SETTINGS_SCROLL_CONTAINER = "QuickSettingsScrollContainer"

const val QUICK_SETTINGS_STREAM_CONFIG_BUTTON = "QuickSettingsStreamConfigButton"
const val QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON = "QuickSettingsConcurrentCameraModeButton"
const val QUICK_SETTINGS_HDR_BUTTON = "QuickSettingsHdrButton"
const val QUICK_SETTINGS_FLASH_BUTTON = "QuickSettingsFlashButton"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,6 @@ enum class CameraAspectRatio : QuickSettingsEnum {
}
}

enum class CameraStreamConfig : QuickSettingsEnum {
MULTI_STREAM {
override fun getDrawableResId() = R.drawable.multi_stream_icon

override fun getTextResId() = R.string.quick_settings_stream_config_multi
override fun getDescriptionResId() = R.string.quick_settings_stream_config_multi_description
},
SINGLE_STREAM {
override fun getDrawableResId() = R.drawable.single_stream_capture_icon

override fun getTextResId() = R.string.quick_settings_stream_config_single
override fun getDescriptionResId() =
R.string.quick_settings_stream_config_single_description
}
}

enum class CameraDynamicRange : QuickSettingsEnum {
SDR {

Expand Down
Loading
Loading