Skip to content

Add front camera screen torch for video capture - #641

Closed
soufiane77-eng wants to merge 1 commit into
google:mainfrom
soufiane77-eng:feature/front-video-screen-torch
Closed

soufiane77-eng wants to merge 1 commit into
google:mainfrom
soufiane77-eng:feature/front-video-screen-torch

Conversation

@soufiane77-eng

Copy link
Copy Markdown

Summary

Adds a screen torch for the front-facing camera while recording video, addressing #299.

Most front-facing cameras have no flash unit, so the screen itself now illuminates the subject during video capture when the flash mode is On or Auto:

  • Screen brightness is maximized for the whole recording.
  • A white frame is drawn around the viewfinder, keeping the preview and the controls (including the stop button) visible and usable.
  • Auto behaves like On, matching the existing front photo screen flash behavior.

The photo screen flash is unchanged.

Implementation

  • Derive isVideoScreenTorchActive in CaptureUiStateAdapter from the existing recording state, lens facing, and flash mode. No camera/event pipeline changes.
  • Render VideoScreenTorchFrame inside the viewfinder slot so it stays behind the controls.
  • Reuse ScreenFlashScreen brightness management for both the photo flash and the video torch, restoring the original brightness once both are inactive. This also covers taking a photo while the video torch is active.

Tests

  • CaptureUiStateAdapterTest: front + On/Auto + active/paused recording enables the torch; Off, back camera, and inactive recording disable it.
  • VideoScreenTorchFrameTest: the frame fills the viewfinder, has a white border, and leaves the center transparent.

Validation note

The local environment has no JDK or Android SDK, so Gradle build, unit tests, lint, and instrumentation tests could not be executed locally (NOT RUN). The changes were reviewed statically (git diff --check), and CI will run the full validation.

Illuminate the subject during front camera video capture by maximizing the screen brightness and drawing a white frame around the viewfinder.

Auto is treated like On, matching the existing front photo screen flash behavior. The brightness is restored once both the photo screen flash and the video screen torch are inactive, including when a photo is taken while recording.

Tests cover the derived UI state and the torch frame rendering.
@google-cla

google-cla Bot commented Nov 5, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a video screen torch feature for front-facing cameras during video recording, which maximizes screen brightness and displays a white frame around the viewfinder. Feedback on the changes includes a style guide violation regarding the naming convention of the VIDEO_SCREEN_TORCH_OVERLAY test tag, an opportunity to simplify the VideoScreenTorchFrame drawing logic by using Modifier.border instead of a custom Canvas, and suggestions to remove unnecessary runTest coroutine builders from tests that do not perform asynchronous operations.


const val PREVIEW_DISPLAY = "PreviewDisplay"
const val SCREEN_FLASH_OVERLAY = "ScreenFlashOverlay"
const val VIDEO_SCREEN_TORCH_OVERLAY = "VideoScreenTorchOverlay"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide (section 10, Test Tags and Semantics), the string value of a test tag must be a lower_snake_case string following the schema element_purpose_value. Please update the string value of VIDEO_SCREEN_TORCH_OVERLAY to follow this convention (e.g., "overlay_video_screen_torch").

Suggested change
const val VIDEO_SCREEN_TORCH_OVERLAY = "VideoScreenTorchOverlay"
const val VIDEO_SCREEN_TORCH_OVERLAY = "overlay_video_screen_torch"
References
  1. Test Tag Naming Convention: When creating a new test tag, the constant name must be in UPPER_SNAKE_CASE, and the string value must be a lower_snake_case string following a specific schema: element_purpose_value. (link)

Comment on lines +142 to +171
fun VideoScreenTorchFrame(modifier: Modifier = Modifier) {
Canvas(
modifier = modifier
.testTag(VIDEO_SCREEN_TORCH_OVERLAY)
.fillMaxSize()
) {
val borderWidth = VIDEO_SCREEN_TORCH_BORDER_WIDTH.toPx()
.coerceAtMost(size.minDimension / 2f)
// Draw four bars instead of a stroked rectangle so that the frame is fully opaque and the
// center of the screen stays transparent.
drawRect(
color = Color.White,
size = Size(width = size.width, height = borderWidth)
)
drawRect(
color = Color.White,
topLeft = Offset(x = 0f, y = size.height - borderWidth),
size = Size(width = size.width, height = borderWidth)
)
drawRect(
color = Color.White,
size = Size(width = borderWidth, height = size.height)
)
drawRect(
color = Color.White,
topLeft = Offset(x = size.width - borderWidth, y = 0f),
size = Size(width = borderWidth, height = size.height)
)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of manually drawing four separate rectangles using a custom Canvas to create a border, you can use a standard Box with Modifier.border. This is more idiomatic in Jetpack Compose, simplifies the code, and avoids manual coordinate calculations.\n\nNote that Modifier.border draws the border inside the boundaries of the composable, and keeps the center transparent, which matches your requirements perfectly. Make sure to import androidx.compose.foundation.border.

@Composable\nfun VideoScreenTorchFrame(modifier: Modifier = Modifier) {\n    Box(\n        modifier = modifier\n            .testTag(VIDEO_SCREEN_TORCH_OVERLAY)\n            .fillMaxSize()\n            .border(width = VIDEO_SCREEN_TORCH_BORDER_WIDTH, color = Color.White)\n    )\n}

Comment on lines +59 to +67
@Test
fun videoScreenTorchFrame_sizeFillsMaxSize() = runTest {
composeTestRule.awaitIdle()
val rootBounds = composeTestRule.onRoot().getBoundsInRoot()
composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY))
.assertWidthIsAtLeast(rootBounds.width)
composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY))
.assertHeightIsAtLeast(rootBounds.height)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test videoScreenTorchFrame_sizeFillsMaxSize does not perform any asynchronous operations or coroutine-based assertions. Using runTest here is unnecessary and can be simplified to a standard test function.

    @Test\n    fun videoScreenTorchFrame_sizeFillsMaxSize() {\n        composeTestRule.awaitIdle()\n        val rootBounds = composeTestRule.onRoot().getBoundsInRoot()\n        composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY))\n            .assertWidthIsAtLeast(rootBounds.width)\n        composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY))\n            .assertHeightIsAtLeast(rootBounds.height)\n    }

Comment on lines +69 to +94
@Test
fun videoScreenTorchFrame_borderIsWhiteAndCenterIsNotWhite() = runTest {
composeTestRule.awaitIdle()
val frameImage =
composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY)).captureToImage()

val borderPixel = IntArray(1)
frameImage.readPixels(
borderPixel,
frameImage.width / 2,
1,
1,
1
)
assertThat(borderPixel[0]).isEqualTo(Color.White.toArgb())

val centerPixel = IntArray(1)
frameImage.readPixels(
centerPixel,
frameImage.width / 2,
frameImage.height / 2,
1,
1
)
assertThat(centerPixel[0]).isNotEqualTo(Color.White.toArgb())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test videoScreenTorchFrame_borderIsWhiteAndCenterIsNotWhite does not use any coroutines or suspend functions. You can remove runTest and define it as a standard test function.

    @Test\n    fun videoScreenTorchFrame_borderIsWhiteAndCenterIsNotWhite() {\n        composeTestRule.awaitIdle()\n        val frameImage =\n            composeTestRule.onNode(hasTestTag(VIDEO_SCREEN_TORCH_OVERLAY)).captureToImage()\n\n        val borderPixel = IntArray(1)\n        frameImage.readPixels(\n            borderPixel,\n            frameImage.width / 2,\n            1,\n            1,\n            1\n        )\n        assertThat(borderPixel[0]).isEqualTo(Color.White.toArgb())\n\n        val centerPixel = IntArray(1)\n        frameImage.readPixels(\n            centerPixel,\n            frameImage.width / 2,\n            frameImage.height / 2,\n            1,\n            1\n        )\n        assertThat(centerPixel[0]).isNotEqualTo(Color.White.toArgb())\n    }

@soufiane77-eng
soufiane77-eng deleted the feature/front-video-screen-torch branch November 5, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant