Skip to content

Migrate Camera2 hardcoded snippets - #1134

Open
barbaralaw wants to merge 6 commits into
android:mainfrom
StellarElements:camera2-snippet-migration
Open

barbaralaw wants to merge 6 commits into
android:mainfrom
StellarElements:camera2-snippet-migration

Conversation

@barbaralaw

@barbaralaw barbaralaw commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Extracts the Kotlin and XML samples from eight Camera2 guides into :camera as
region-tagged source under com.example.camera.snippets.camera2, so the guides
can import them instead of hardcoding them. CameraX already lives in :camera
on main; this PR only adds the Camera2 samples (and the extra dependencies
they need). CameraX sources are unchanged.

44 snippets across 8 pages. 18 publish exactly what the page shows. 26 differ, every one
listed below with a reason.
Line wrapping is not counted as a difference: spotlessApply
runs before submission and rewraps long calls without changing what a reader reads.

Region tags all begin android_camera2_; the tables drop that prefix.

How to read the "why" column

Why What a reader of the page would see
// ... The page prints a standalone ... on its own line (omitted arguments or omitted statements). The region publishes the same ellipsis, either as a // ... line comment or as a plain [START_EXCLUDE] block that DevSite renders as ....
/* ... */ The page prints ... as the right-hand side of a typed val that still has a trailing // from … comment. A line comment would swallow that note, so the region wraps the ellipsis in a block comment on the same line.
null safety The page either dereferences a platform-nullable characteristics.get(KEY) directly (which does not compile) or uses !!. Per review, the region publishes no !!: it uses a safe call (?.), an elvis default or early return (?:), or checkNotNull(...) where the function must return a non-null value.
review: modernized Changed at reviewer request to replace a deprecated or fragile pattern (the AsyncTask.SERIAL_EXECUTOR default, or hopping back to the UI thread with v.post from a background coroutine).
generic invisible on live page On three pages the type arguments never make it into copyable Kotlin snippets. The rendered code block shows List and CompletableDeferred? = null without type arguments. The .md.txt export of the same blocks is the same: no <Unit>, no <Surface>. So the sample does not compile in either form. Though the HTML file still has the type arguments <Surface> (and <Unit>, <DualCamera>, <OutputConfiguration>), they're unescaped inside the <pre>, so the parser treats them as tags — that is why both the browser and the export lose them. This is not a CSS issue; the page source has to escape those brackets (or stop writing raw <Type> in the block). The proposed migrated regions publish the type arguments the samples need. Listed again under "Live snippet fixes this surfaced".
hardcoded snippet defect fixed The published Kotlin does not compile, and this is not a sample meant to demonstrate an error. The region publishes the corrected line.
Spotless ktlint changed a published line. Reported, not argued with.

Per page

1. Camera capture sessions and requests

https://developer.android.com/media/camera/camera2/capture-sessions-requests

5 snippets, 0 match as published, 5 differ.

Snippet Page section Difference Why
capture_sessions_requests_configure_session Create a CameraCaptureSession 2 omitted-argument ellipses rendered by plain [START_EXCLUDE] blocks, 1 standalone ellipsis as a line comment; comment continued as // reasons // ...
capture_sessions_requests_single_request Single CaptureRequests 1 inline ellipsis wrapped /* ... */
capture_sessions_requests_capture Single CaptureRequests bare ... kept; CaptureRequest → CaptureRequest.Builder hardcoded snippet defect fixed
capture_sessions_requests_repeating_request Repeat CaptureRequests bare ... kept; CaptureRequest → CaptureRequest.Builder hardcoded snippet defect fixed
capture_sessions_requests_interleaved_requests Interleave CaptureRequests 1 inline ellipsis wrapped /* ... */

createCaptureRequest() returns a CaptureRequest.Builder. The live capture and
repeating_request samples type that local as CaptureRequest and then call .build().

The live repeating sample comments out session.setRepeatingRequest(...); per review, the
region keeps that line commented out to match the page.

configure_session publishes // reasons as its own comment line so the file compiles.

2. Camera lenses and capabilities

https://developer.android.com/media/camera/camera2/camera-enumeration

3 snippets, 1 match, 2 differ.

Snippet Page section Difference Why
camera_enumeration_iterate_cameras opening block, before the first heading 2 standalone ellipses as line comments // ...
camera_enumeration_get_first_camera_id_facing Choose the first available camera return null added inside catch hardcoded snippet defect fixed

Matching as published: filter_cameras.

The live get_first_camera_id_facing sample is a String? function whose catch only logs.
Kotlin requires a return on that path; null matches firstOrNull() on the success path.

3. Use multiple camera streams simultaneously

https://developer.android.com/media/camera/camera2/multiple-camera-streams-simultaneously

11 snippets, 5 match, 6 differ.

Snippet Page section Difference Why
multiple_camera_streams_supported_formats Output type ?.outputFormats safe call null safety
multiple_camera_streams_output_sizes_format Output size ?.getOutputSizes(...) safe call null safety
multiple_camera_streams_output_sizes_class Output size ?.getOutputSizes(...) safe call null safety
multiple_camera_streams_maximum_output_size Output size config lookup wrapped in checkNotNull(...) null safety
multiple_camera_streams_preview_output_size Output size page's !! on the config lookup → checkNotNull(...); "SmartSize(${long}x${short})" → "SmartSize(${long}x$short)" null safety, Spotless
multiple_camera_streams_surfaceview_callback Output to a SurfaceView standalone ... in the SurfaceHolder.Callback rendered by a plain [START_EXCLUDE] block around the stub overrides // ...

getMaximumOutputSize() and getPreviewOutputSize() return a non-null Size, so a safe
call would change their signatures; checkNotNull(...) keeps them intact.

Matching as published: combined_request, hardware_level, get_preview_output_sizes,
image_reader_instance, image_reader_listener.

4. Screen flash

https://developer.android.com/media/camera/camera2/screen-flash-implementation-guidelines

9 snippets, 5 match, 4 differ.

Snippet Page section Difference Why
screen_flash_capture_callback_ae_mode Capture callback that can wait for AE mode to be updated publishes CompletableDeferred<Unit>; page renders CompletableDeferred generic invisible on live page
screen_flash_capture_callback_convergence Capture callback code update to wait for convergence two <Unit> type arguments; || moved to line ends past an inline comment generic invisible on live page, Spotless
screen_flash_run_precapture_sequence Precapture sequence triggering and waiting publishes CompletableDeferred<Unit> generic invisible on live page
screen_flash_stitch_together Stitch everything together lifecycleScope.launch(Dispatchers.IO) { … v.post { … } } → lifecycleScope.launch { withContext(Dispatchers.IO) { … } … }; unused v -> dropped; all original step comments kept, two short comments added review: modernized

Matching as published: overlay_view (XML), brightness_control, check_ae_mode,
enable_disable_ae_mode, setup_repeating_request.

The wrapper activity (outside every region) extends ComponentActivity, so :camera does
not depend on appcompat.

5. Camera preview

https://developer.android.com/media/camera/camera2/camera-preview

2 snippets extracted, 1 match, 1 differs.

Snippet Page section Difference Why
camera_preview_compute_relative_rotation Relative rotation page's !! on SENSOR_ORIENTATION → ?: 0 null safety

Matching as published: surfaceview_onmeasure.

Not extracted: viewfinder_request_surface (CameraViewfinder). The page still
documents CameraViewfinder, requestSurfaceAsync(), a ViewfinderSurfaceRequest
that takes CameraCharacteristics, a ListenableFuture of Surface, and
markSurfaceSafeToRelease(). Those types were deprecated and then removed; the
shipping widget is ViewfinderView, the request is width/height only, and the
async API returns a ViewfinderSurfaceSession that must be closed. Publishing
the live snippet would compile only against local stand-ins, and rewriting it
to the current API is a docs change, not a migration. Recommend DevRel rework
that whole CameraViewfinder section (Kotlin, Java twin, and surrounding prose)
before it is imported.

6. HDR video capture

https://developer.android.com/media/camera/camera2/hdr-video-capture

6 snippets, 3 match, 3 differ.

Snippet Page section Difference Why
hdr_video_capture_is_ten_bit_profile_supported Check for HDR support page's !! in the for loop → ?: return false on the capabilities lookup null safety
hdr_video_capture_is_hlg_supported Check for HDR support Val → val; stray ; after return false dropped; page's !! → ?.getSupportedProfiles() and ?.contains(...) ?: false hardcoded snippet defect fixed, null safety
hdr_video_capture_setup_session Set up HDR capture publishes List<Surface> and MutableList<OutputConfiguration> type arguments generic invisible on live page

Matching as published: preview_request, record_request, encode_hdr_stream.

7. Multi-camera API

https://developer.android.com/media/camera/camera2/multi-camera

6 snippets, 1 match, 5 differ.

Snippet Page section Difference Why
multi_camera_find_dual_cameras Using a pair of physical cameras MutableList<DualCamera>() → mutableListOf<DualCamera>(); type argument published; page's !!.contains(...) → ?.contains(...) ?: false hardcoded snippet defect fixed, generic invisible on live page, null safety
multi_camera_open_dual_camera Using a pair of physical cameras AsyncTask.SERIAL_EXECUTOR default and its deprecation comment removed; executor: Executor is now required review: modernized
multi_camera_create_dual_camera_session Using a pair of physical cameras three <Surface> type arguments published; AsyncTask.SERIAL_EXECUTOR default and comment removed; executor: Executor is now required generic invisible on live page, review: modernized
multi_camera_find_short_long_camera_pair Zoom example use-case page's maxOrNull()!! / minOrNull()!! → ?: 0f defaults null safety
multi_camera_zoom_example Zoom example use-case page's findShortLongCameraPair(manager)!! → ?: return; createDualCameraSession(...) passes executor = cameraExecutor; val cameraExecutor: Executor = ... // owned by your activity / fragment placeholder added next to the camera manager placeholder null safety, review: modernized

Matching as published: distortion_correction.

MutableList has no constructor, so the page's line cannot compile as written.

The executor is now caller-owned rather than a default argument. Defaulting to a new
executor would create a thread pool per call that nothing shuts down; a required parameter
matches the page's Java openDualCamera, which already took an Executor, and the
framework's own openCamera(id, executor, callback).

8. Camera2 Extensions API

https://developer.android.com/media/camera/camera2/extensions-api

2 snippets, both match as published: get_extension_camera_ids, start_extension_session.

This page's snippet calls Snackbar.make(...), so :camera gains
implementation(libs.google.android.material) and the file imports
com.google.android.material.snackbar.Snackbar outside every region — the published
snippet is unchanged. The alternative was a compile-only Snackbar stand-in and no Gradle
change; that avoids pulling Views Material into the module, but it mocks an API
the page actually documents.

Not extracted

Kotlin only. Every page above except HDR video capture carries a Java twin for each of its
Kotlin blocks; those are left for the page edit that retires them. Camera preview also has a
one-line orientation formula under "Orientation calculation" that is prose, not a compilable
sample.

Camera preview's CameraViewfinder sample (viewfinder_request_surface) is held
out of this PR on purpose; see §5. The SurfaceView onMeasure and relative-rotation
samples from the same page are in :camera.

Live snippet fixes this surfaced

  1. Missing type arguments in the sample source on multi-camera, HDR video capture, and screen flash. Copy-paste from the page and from .md.txt both yield List, CompletableDeferred?, mutableListOf() — not List<Surface> / CompletableDeferred<Unit>? / mutableListOf<OutputConfiguration>(). The HTML still contains unescaped <Surface> (etc.) inside the code block; that is markup, not Kotlin, which is why the export is empty of generics too.
  2. HDR video capture: Val cameraCharacteristics = ... and a stray ; after
    return false.
  3. Multi-camera: MutableList<DualCamera>() — MutableList is an interface with no
    constructor.
  4. Camera preview, CameraViewfinder section: the widget was renamed to ViewfinderView and the request/surface APIs in the sample no longer exist on the stable viewfinder-view artifact (1.5+). The section needs a rewrite, not an import of the current Kotlin/Java fences.
  5. Capture sessions: val captureRequest: CaptureRequest = ... then .build() — createCaptureRequest() returns CaptureRequest.Builder.
  6. Camera enumeration: getFirstCameraIdFacing is String? but the live catch only logs, so the function does not compile. The region returns null from the catch.
  7. Multi-camera zoom example: the page declares val cameraManager: CameraManager = ... but the code below calls findShortLongCameraPair(manager) and createDualCameraSession(manager, ...). The region keeps the page's naming; renaming one side is a small follow-up.

Review follow-up (584f960)

  • Plain [START_EXCLUDE] (no silent plus hand-written // ...) in configure_session and surfaceview_callback.
  • setRepeatingRequest kept commented out, matching the page.
  • No !! anywhere in the camera2 snippets (see null safety).
  • AsyncTask.SERIAL_EXECUTOR default replaced with a required executor parameter.
  • ComponentActivity instead of AppCompatActivity; appcompat dependency removed.
  • withContext(Dispatchers.IO) instead of v.post in stitch_together.

Region tags are unchanged by this commit.

Verification

  • ./gradlew :camera:compileDebugKotlin — passes
  • ./gradlew :camera:lintDebug — passes
  • ./gradlew :camera:spotlessApply — run before pushing; the branch is clean afterwards

@barbaralaw
barbaralaw marked this pull request as ready for review September 22, 2026 21:32
@barbaralaw
barbaralaw requested a review from a team as a code owner September 22, 2026 21:32
@snippet-bot

snippet-bot Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Here is the summary of changes.

You are about to add 44 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@kkuan2011 kkuan2011 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.

Thanks Barbara! AI review flagged one issue. Can you update and send to Erik for review? Thank you!

@kkuan2011 kkuan2011 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.

Looks good! Minor nits and some comments to remove appcompat and to replace !! with ?.

Ready for SME review after those are addressed!

Comment thread camera/build.gradle.kts Outdated
Comment thread camera/src/main/java/com/example/camera/snippets/camera2/MultiCamera.kt Outdated
Comment thread camera/src/main/java/com/example/camera/snippets/camera2/MultiCamera.kt Outdated
Comment thread camera/src/main/java/com/example/camera/snippets/camera2/MultiCamera.kt Outdated
Rebase the Camera2 migration onto upstream main after the :camera parent module landed, keeping CameraX sources unchanged and adding the Camera2 samples plus the AppCompat, Fragment, Material, and coroutines dependencies they need.
- Use plain START_EXCLUDE (no silent + hand-written // ...) in
  CaptureSessionsRequests and the SurfaceView callback snippet.
- Keep setRepeatingRequest commented out, matching the DAC page.
- Remove all !! in the camera2 snippets: safe calls / elvis defaults,
  ?: return in zoomExample, checkNotNull where a non-null Size is returned.
- Replace the deprecated AsyncTask.SERIAL_EXECUTOR default with a required
  executor parameter in openDualCamera and createDualCameraSession; the
  zoom example passes a caller-owned cameraExecutor placeholder.
- Use ComponentActivity instead of AppCompatActivity and drop appcompat.
- Replace the v.post hop with withContext(Dispatchers.IO) in the screen
  flash stitch-together snippet.
@barbaralaw
barbaralaw force-pushed the camera2-snippet-migration branch from 584f960 to 2d9e8b1 Compare September 30, 2026 20:39

This branch has not been deployed

No deployments
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.

3 participants