Skip to content

Commit

Permalink
Display current camera physical Id on preview screen (#257)
Browse files Browse the repository at this point in the history
* Display current camera physical Id on preview screen

* Update PreviewScreenComponents.kt

* Update CameraXCameraUseCaseTest.kt

* Update ScreenFlashTest.kt

* add custom intent action

* logical id and key debug mode

* update

* update

* Update PreviewViewModelTest.kt

* Update CameraXCameraUseCase.kt

* address comments

* Update CameraXCameraUseCase.kt

* Update PreviewScreenComponents.kt

* update
  • Loading branch information
davidjiagoogle authored Aug 28, 2024
1 parent 9a74762 commit a2d762d
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 15 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/com/google/jetpackcamera/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

private const val TAG = "MainActivity"
private const val KEY_DEBUG_MODE = "KEY_DEBUG_MODE"

/**
* Activity for the JetpackCameraApp.
Expand Down Expand Up @@ -137,6 +138,7 @@ class MainActivity : ComponentActivity() {
) {
JcaApp(
previewMode = getPreviewMode(),
isDebugMode = isDebugMode,
openAppSettings = ::openAppSettings,
onRequestWindowColorMode = { colorMode ->
// Window color mode APIs require API level 26+
Expand All @@ -160,6 +162,9 @@ class MainActivity : ComponentActivity() {
}
}

private val isDebugMode: Boolean
get() = intent?.getBooleanExtra(KEY_DEBUG_MODE, false) ?: false

private fun getStandardMode(): PreviewMode.StandardMode {
return PreviewMode.StandardMode { event ->
if (event is PreviewViewModel.ImageCaptureEvent.ImageSaved) {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/google/jetpackcamera/ui/JcaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ fun JcaApp(
/*TODO(b/306236646): remove after still capture*/
previewMode: PreviewMode,
modifier: Modifier = Modifier,
isDebugMode: Boolean,
onRequestWindowColorMode: (Int) -> Unit,
onFirstFrameCaptureCompleted: () -> Unit
) {
JetpackCameraNavHost(
previewMode = previewMode,
isDebugMode = isDebugMode,
onOpenAppSettings = openAppSettings,
onRequestWindowColorMode = onRequestWindowColorMode,
onFirstFrameCaptureCompleted = onFirstFrameCaptureCompleted,
Expand All @@ -59,6 +61,7 @@ fun JcaApp(
private fun JetpackCameraNavHost(
modifier: Modifier = Modifier,
previewMode: PreviewMode,
isDebugMode: Boolean,
onOpenAppSettings: () -> Unit,
onRequestWindowColorMode: (Int) -> Unit,
onFirstFrameCaptureCompleted: () -> Unit,
Expand Down Expand Up @@ -102,7 +105,8 @@ private fun JetpackCameraNavHost(
onNavigateToSettings = { navController.navigate(SETTINGS_ROUTE) },
onRequestWindowColorMode = onRequestWindowColorMode,
onFirstFrameCaptureCompleted = onFirstFrameCaptureCompleted,
previewMode = previewMode
previewMode = previewMode,
isDebugMode = isDebugMode
)
}
composable(SETTINGS_ROUTE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class CameraXCameraUseCaseTest {
Dispatchers.Default,
constraintsRepository
).apply {
initialize(appSettings, false)
initialize(appSettings, CameraUseCase.UseCaseMode.STANDARD)
providePreviewSurface()
}

Expand Down Expand Up @@ -183,7 +183,7 @@ class CameraXCameraUseCaseTest {
val onRecorded = CompletableDeferred<Unit>()
val onRecordStatus = CompletableDeferred<Unit>()
var statusCount = 0
startVideoRecording {
startVideoRecording(null, false) {
when (it) {
is OnVideoRecorded -> {
val videoUri = it.savedUri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,8 @@ interface CameraUseCase {
data class CameraState(
val zoomScale: Float = 1f,
val sessionFirstFrameTimestamp: Long = 0L,
val torchEnabled: Boolean = false
val torchEnabled: Boolean = false,
val debugInfo: DebugInfo = DebugInfo(null, null)
)

data class DebugInfo(val logicalCameraId: String?, val physicalCameraId: String?)
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import android.content.pm.PackageManager
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.TotalCaptureResult
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.SystemClock
import android.provider.MediaStore
Expand Down Expand Up @@ -150,12 +152,30 @@ constructor(
private fun setOnCaptureCompletedCallback(previewBuilder: Preview.Builder) {
val isFirstFrameTimestampUpdated = atomic(false)
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
private var physicalCameraId: String? = null
private var logicalCameraId: String? = null
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
super.onCaptureCompleted(session, request, result)
var physicalCameraId: String? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
physicalCameraId = result.get(
CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
)
}
val logicalCameraId = session.device.id
if (!physicalCameraId.equals(this.physicalCameraId) ||
logicalCameraId != this.logicalCameraId
) {
_currentCameraState.update { old ->
old.copy(
debugInfo = DebugInfo(logicalCameraId, physicalCameraId)
)
}
}
try {
if (!isFirstFrameTimestampUpdated.value) {
_currentCameraState.update { old ->
Expand Down Expand Up @@ -706,7 +726,7 @@ constructor(

val pendingRecord = if (shouldUseUri) {
val fileOutputOptions = FileOutputOptions.Builder(
File(videoCaptureUri!!.getPath())
File(videoCaptureUri!!.path!!)
).build()
videoCaptureUseCase.output.prepareRecording(application, fileOutputOptions)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ private const val TAG = "PreviewScreen"
fun PreviewScreen(
onNavigateToSettings: () -> Unit,
previewMode: PreviewMode,
isDebugMode: Boolean,
modifier: Modifier = Modifier,
onRequestWindowColorMode: (Int) -> Unit = {},
onFirstFrameCaptureCompleted: () -> Unit = {},
viewModel: PreviewViewModel = hiltViewModel<PreviewViewModel, PreviewViewModel.Factory>
{ factory -> factory.create(previewMode) }
{ factory -> factory.create(previewMode, isDebugMode) }
) {
Log.d(TAG, "PreviewScreen")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ sealed interface PreviewUiState {
val lastBlinkTimeStamp: Long = 0,
val previewMode: PreviewMode,
val captureModeToggleUiState: CaptureModeToggleUiState,
val sessionFirstFrameTimestamp: Long = 0L
val sessionFirstFrameTimestamp: Long = 0L,
val currentPhysicalCameraId: String? = null,
val currentLogicalCameraId: String? = null,
val isDebugMode: Boolean = false
) : PreviewUiState
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private const val IMAGE_CAPTURE_TRACE = "JCA Image Capture"
@HiltViewModel(assistedFactory = PreviewViewModel.Factory::class)
class PreviewViewModel @AssistedInject constructor(
@Assisted val previewMode: PreviewMode,
@Assisted val isDebugMode: Boolean,
private val cameraUseCase: CameraUseCase,
private val settingsRepository: SettingsRepository,
private val constraintsRepository: ConstraintsRepository
Expand Down Expand Up @@ -141,7 +142,10 @@ class PreviewViewModel @AssistedInject constructor(
captureModeToggleUiState = getCaptureToggleUiState(
systemConstraints,
cameraAppSettings
)
),
isDebugMode = isDebugMode,
currentLogicalCameraId = cameraState.debugInfo.logicalCameraId,
currentPhysicalCameraId = cameraState.debugInfo.physicalCameraId
)

is PreviewUiState.NotReady ->
Expand All @@ -154,7 +158,10 @@ class PreviewViewModel @AssistedInject constructor(
captureModeToggleUiState = getCaptureToggleUiState(
systemConstraints,
cameraAppSettings
)
),
isDebugMode = isDebugMode,
currentLogicalCameraId = cameraState.debugInfo.logicalCameraId,
currentPhysicalCameraId = cameraState.debugInfo.physicalCameraId
)
}
}
Expand Down Expand Up @@ -738,7 +745,7 @@ class PreviewViewModel @AssistedInject constructor(

@AssistedFactory
interface Factory {
fun create(previewMode: PreviewMode): PreviewViewModel
fun create(previewMode: PreviewMode, isDebugMode: Boolean): PreviewViewModel
}

sealed interface ImageCaptureEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material.icons.outlined.CameraAlt
import androidx.compose.material.icons.outlined.Videocam
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -48,6 +49,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.util.Preconditions
import com.google.jetpackcamera.feature.preview.CaptureModeToggleUiState
import com.google.jetpackcamera.feature.preview.MultipleEventsCutter
Expand Down Expand Up @@ -135,6 +137,8 @@ fun CameraControlsOverlay(
previewUiState = previewUiState,
audioAmplitude = previewUiState.audioAmplitude,
zoomLevel = previewUiState.zoomScale,
physicalCameraId = previewUiState.currentPhysicalCameraId,
logicalCameraId = previewUiState.currentLogicalCameraId,
showZoomLevel = zoomLevelDisplayState.showZoomLevel,
isQuickSettingsOpen = previewUiState.quickSettingsIsOpen,
currentCameraSettings = previewUiState.currentCameraSettings,
Expand Down Expand Up @@ -204,6 +208,8 @@ private fun ControlsBottom(
modifier: Modifier = Modifier,
audioAmplitude: Double,
previewUiState: PreviewUiState.Ready,
physicalCameraId: String? = null,
logicalCameraId: String? = null,
zoomLevel: Float,
showZoomLevel: Boolean,
isQuickSettingsOpen: Boolean,
Expand All @@ -230,8 +236,17 @@ private fun ControlsBottom(
onStopVideoRecording: () -> Unit = {}
) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
if (showZoomLevel) {
ZoomScaleText(zoomLevel)
CompositionLocalProvider(
LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 20.sp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (showZoomLevel) {
ZoomScaleText(zoomLevel)
}
if (previewUiState.isDebugMode) {
CurrentCameraIdText(physicalCameraId, logicalCameraId)
}
}
}

Row(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
Expand Down Expand Up @@ -89,7 +90,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.jetpackcamera.feature.preview.PreviewUiState
import com.google.jetpackcamera.feature.preview.R
import com.google.jetpackcamera.feature.preview.VideoRecordingState
Expand Down Expand Up @@ -432,11 +432,30 @@ fun ZoomScaleText(zoomScale: Float) {
modifier = Modifier
.alpha(contentAlpha.value)
.testTag(ZOOM_RATIO_TAG),
text = "%.1fx".format(zoomScale),
fontSize = 20.sp
text = stringResource(id = R.string.zoom_scale_text, zoomScale)
)
}

@Composable
fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row {
Text(text = stringResource(R.string.debug_text_logical_camera_id_prefix))
Text(
modifier = Modifier.testTag(LOGICAL_CAMERA_ID_TAG),
text = logicalCameraId ?: "---"
)
}
Row {
Text(text = stringResource(R.string.debug_text_physical_camera_id_prefix))
Text(
modifier = Modifier.testTag(PHYSICAL_CAMERA_ID_TAG),
text = physicalCameraId ?: "---"
)
}
}
}

@Composable
fun CaptureButton(
onClick: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ const val HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG = "HdrImageUnsupportedOnMult
const val HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG = "HdrVideoUnsupportedOnDeviceTag"
const val HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG = "HdrVideoUnsupportedOnDeviceTag"
const val ZOOM_RATIO_TAG = "ZoomRatioTag"
const val LOGICAL_CAMERA_ID_TAG = "LogicalCameraIdTag"
const val PHYSICAL_CAMERA_ID_TAG = "PhysicalCameraIdTag"
4 changes: 4 additions & 0 deletions feature/preview/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
<string name="flip_camera_content_description">Flip Camera</string>

<string name="audio_visualizer_icon">An icon of a microphone</string>
<string name="zoom_scale_text">%1$.2fx</string>

<string name="debug_text_physical_camera_id_prefix">Physical ID: </string>
<string name="debug_text_logical_camera_id_prefix">Logical ID: </string>

<string name="toast_image_capture_success">Image Capture Success</string>
<string name="toast_video_capture_success">Video Capture Success</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class PreviewViewModelTest {
Dispatchers.setMain(StandardTestDispatcher())
previewViewModel = PreviewViewModel(
PreviewMode.StandardMode {},
false,
cameraUseCase = cameraUseCase,
constraintsRepository = constraintsRepository,
settingsRepository = FakeSettingsRepository
Expand Down

0 comments on commit a2d762d

Please sign in to comment.