DEV Community

Super Funicular
Super Funicular

Posted on

One Camera2 Key, Three Layers That Can Set It: What the CameraX 1.7 Reference Says About Which One Wins

Short answer: CameraX now lets you hand a raw Camera2 capture request key to the camera from several different attachment points, and they do not merge symmetrically. The reference page for Camera2Interop states the order rather than leaving it to a device: a key applied through CameraControl overwrites the same key set on the session configuration, runtime calls accumulate until something clears them, and a still capture starts by copying the repeating keys and then overrides them. On top of that, the class documentation says interop can override CameraX's own internal configuration. If a camera on your desk runs for ten minutes at a time, you will probably never notice. If one runs for days with nobody in front of it, the layer you set a key on is a decision worth making on purpose.

The door that just got deprecated

Anyone who has built a long-running Android camera has eventually wanted something CameraX does not expose directly — a capture request key, a template type, a raw session callback. The way you reached it was Camera2Interop.Extender.

As of the CameraX release dated August 12, 2026, it is deprecated. The reference page's own note on the nested class reads:

"This class is deprecated. Use the camera2Interop extension function on UseCase.Builder (e.g., 'builder.camera2Interop { setPhysicalCameraId(physicalCameraId) }') or SessionConfig.Builder for capture request options and callbacks instead."

And the release notes for 1.7.0-alpha03 put it in one line:

"Deprecated legacy Camera2Interop APIs (Camera2Interop.Extender, Camera2CameraControl, Camera2CameraInfo, and CaptureRequestOptions) in favor of Camera2Interop configurator factory methods and Kotlin extension functions."

Four public surfaces in one bullet. Worth noting before that reads as more dramatic than it is: Camera2Interop.Extender carries an @ExperimentalCamera2Interop annotation on the reference page, so this was never a stable API being pulled out from under anyone. What is new is that the replacement is not experimental, and that there is more than one of it.

The replacement arrived in two spellings

Read the deprecation note again and it points at a Kotlin extension function. Read the release-notes bullet and it points at factory methods. Both are correct, because the same release added both. Here is the factory-method entry:

"Added new Camera2Interop configurator factory methods (forUseCase, forImageCapture, forSessionConfig, forCameraControl) and corresponding setInterop / applyInteropAsync methods on Preview.Builder, ImageCapture.Builder, ImageAnalysis.Builder, VideoCapture.Builder, SessionConfig.Builder, and CameraControl."

And immediately above it in the same version, the Kotlin one:

"Added Kotlin DSL camera2Interop extension blocks for preview, imageAnalysis, videoCapture, imageCapture, and SessionConfig.Builder, as well as CameraControl.applyCamera2InteropAsync extension function."

So the four configurator factories are one path, and a set of Kotlin DSL blocks plus applyCamera2InteropAsync is a parallel one. This matters less as an API-surface complaint than as a practical one, and I will come back to it at the end: two spellings of the same write means a search for "who else sets this key" has to look for both.

The class documentation groups the factories by what they attach to, and the grouping is the useful part:

"Apply Camera2 configuration to androidx.camera.core.UseCase builders using forUseCase, forImageCapture, or forSessionConfig"

"Apply Camera2 configuration to active camera controls using forCameraControl"

Three of them configure something you are building. One of them configures something already running. That split — bind time on one side, runtime on the other — is the whole subject of this article.

The precedence rules, in the reference's own words

The ordering is not left to be worked out against a device. It is written down, and it is written down in three separate places.

Runtime beats bind time. On forCameraControl:

"This overwrites options set with SessionConfigCamera2Interop via androidx.camera.core.SessionConfig.Builder.setInterop."

That is unambiguous. A key you set once, carefully, when you built the session is replaced by the same key applied later through applyInteropAsync. No error, no warning, no return value to inspect — the later call simply takes precedence. This is the sentence to remember.

Runtime calls accumulate rather than replace. Also on forCameraControl:

"Subsequent calls to androidx.camera.core.CameraControl.applyInteropAsync update parameters incrementally without clearing previously set keys, unless explicitly cleared using CameraControlCamera2Interop.clearCaptureRequestOption or CameraControlCamera2Interop.clearAllCaptureRequestOptions."

Two consequences, and they pull against each other.

The good one: you can adjust a single key at runtime without rebuilding the whole set. The one you set an hour ago survives.

The awkward one: the one you set an hour ago survives. There is no natural point at which the accumulated state resets itself. If some code path applies a key under a condition that later stops holding, the key stays applied until something clears it. clearCaptureRequestOption and clearAllCaptureRequestOptions are listed on the same page as first-class operations, which is what you would expect of an API whose state is sticky by design — and they are the parts most likely to be missing from a first implementation.

Still captures start from the repeating keys, then override. On forImageCapture:

"The capture request keys for one-shot still captures (such as androidx.camera.core.ImageCapture.takePicture) are determined by copying all repeating request keys (which may include keys added via androidx.camera.core.SessionConfig.Builder.setInterop or androidx.camera.core.CameraControl.applyInteropAsync) and then overriding them with the still capture request keys configured here."

Read that as an algorithm rather than a sentence and it is a two-step merge: copy everything the repeating request is carrying, then let the still-capture keys win on any collision. It also means a still capture inherits whatever runtime state has accumulated on the repeating request — including the sticky key from the paragraph above.

The line that should slow you down

Above all the per-method detail, the class documentation carries a general warning:

"Using Camera2 interop options can override internal CameraX configurations."

and then makes it concrete:

"If an option configured via interop conflicts with options required by CameraX internally, the option from Camera2Interop will override, which may result in unexpected behavior or interfere with CameraX functionality."

The direction of that sentence is the important part. Interop does not lose to CameraX. Interop wins, including against options CameraX describes as required for its own operation. The library is telling you plainly that the way out is not sandboxed, and that a conflict resolves in favour of whatever you passed in.

That is a reasonable design — a way around a library that the library could overrule would be useless — but it puts the burden of knowing what CameraX needs onto the caller, and CameraX's internal requirements are not a published list.

Raw callbacks are the sharp edge

Three of the four factories can carry capture callbacks, and the reference repeats a warning under each of them. This is the wording under forCameraControl:

"Directly invoking state-altering methods on these raw objects (such as android.hardware.camera2.CameraCaptureSession.close or android.hardware.camera2.CameraCaptureSession.abortCaptures) bypasses CameraX pipeline management and may cause state desynchronization, stream interruption, or application crashes."

Reading the objects handed to a callback is fine. Calling methods on them that change state is not, because CameraX is still the owner of that session and has its own idea of what state it is in. The failure mode named there — state desynchronization — is the one that will not reproduce while you are testing, because it needs a real sequence of events to get the two views of the session apart.

Why an unattended camera gets this wrong differently

A camera app someone is holding gets restarted constantly. The activity goes away, the session is rebuilt, accumulated state is discarded, and a mistake in the layering shows up within a minute of somebody looking at the screen.

A camera that runs for days on a shelf has none of that. The session is built once. Nothing rebuilds it on a schedule. Whatever key some code path applied through applyInteropAsync at hour three is still applied at hour forty, because the documented behaviour is that it accumulates. And there is no live view being watched for the moment it starts looking wrong.

So the practical question is not does my interop key work. It is which layer owns this key, and is there any other code path that also writes it. We ran into the general shape of this problem while writing about keeping focus and exposure sane in a background Camera2 session; the interop layering is the same class of problem one level up.

One owner per key

The rule that falls out of the quoted passages is short.

  1. Pick one layer per key and record the choice. Session configuration for anything that should hold for the life of the session. CameraControl for anything that genuinely varies at runtime. Never both for the same key.
  2. If you use applyInteropAsync, decide up front who clears. Sticky state with no owner is the failure that takes a week to find. clearCaptureRequestOption for one key, clearAllCaptureRequestOptions to reset.
  3. Treat still-capture keys as a deliberate override, not a default. They are copied on top of the repeating keys, so leaving one set is a standing instruction, not a one-off.
  4. Read raw session objects, never drive them. Anything that changes session state belongs to CameraX.
  5. Search for the second writer before you debug the first — in both spellings. Given a documented last-write-wins rule, two writers to one key is the bug. And because this release shipped a Java factory path and a Kotlin DSL path together, grepping for applyInteropAsync alone can miss an applyCamera2InteropAsync call sitting in a Kotlin file two directories away. It will look like a device quirk right up until you find it.

What this article does not establish

Being straight about the limits, because a reference page is documentation, not measurement:

  • These are documented behaviours, not results I measured. Everything above is quoted from the reference page and the release notes. I have not run a matrix of devices to confirm that every implementation honours the stated ordering, and a camera HAL can surprise you.
  • 1.7.0-alpha03 is an alpha, and it is still the current one. Checked on 6 September 2026: it remains the newest 1.7 entry on the release-notes page, which is why the August 12 date below is still the right one to quote. Shapes in this area are moving, though, and an article about an alpha has a short shelf life by construction.
  • I have not verified that the two replacement paths are exactly equivalent. The release notes list the factory methods and the Kotlin DSL blocks as separate additions in the same version. Whether applyCamera2InteropAsync and applyInteropAsync differ in anything beyond spelling is not something the release notes settle, and I have not read the source to find out.
  • The reference does not publish the list of options CameraX requires internally. The warning says a conflict resolves in favour of interop; it does not enumerate what you might be conflicting with. If someone knows where that list lives, I would like to read it.
  • Nothing here is a claim about what any particular app does. Which layer a given camera app sets its keys on is not something you can read off a documentation page, and this article does not guess.

The short version

CameraX 1.7 deprecated one interop door and shipped two overlapping ways to replace it, and the places you can attach a key from now outnumber the places a reader would think to check. The reference page tells you exactly how they resolve: runtime overwrites the session configuration, runtime calls accumulate until you clear them, still captures copy the repeating keys and then override, and interop as a whole outranks CameraX's own internal configuration.

The layering is not visible in your own code, which is why one owner per key, recorded somewhere the next person will find it, is worth more here than it sounds.


Related reading: The Preview Is Flipped and the Recording Is Not works through a different bullet in this same 1.7.0-alpha03 release — the setMirrorMode one. That piece is about two settings with two different defaults that disagree until you make them agree; this one is about one setting with several writers and a stated order. Same release notes, opposite failure.

Sources, all first-party: the Camera2Interop reference and the CameraX release notes, both on developer.android.com, read 6 September 2026. Quotations are verbatim; the dated claim comes from the Version 1.7.0-alpha03 heading, not the page footer.

We build Background Camera RemoteStream, an Android camera that keeps recording with the screen off and serves a picture over your own network. More writing at superfunicular.com.

Top comments (0)