Let a join read the files the user actually picked #239

Merged
JMR-dev merged 1 commits from test/content-uri-reaches-ffmpeg into main 2026-09-06 06:51:10 +00:00
JMR-dev commented 2026-09-06 05:36:04 +00:00 (Migrated from github.com)

Fixes #238. Closes #225. Settles the open question on #226.

Joining files picked through the system picker failed outright whenever the strategy was stream copy — the matched-files case the UI advertises as "joined without re-encoding — no quality loss".

[ffkitsaf @ ...] Protocol 'ffkitsaf' not on whitelist 'file,crypto,data'!
Error opening input file .../joined_from_content.concat_list.txt

This was found by writing #225's test, not by looking for it.

The defect

JoinScreen picks with OpenMultipleDocuments, so real inputs are always content://. ConcatEngine maps each through getSafParameterForRead, and FFmpegConcatCommand writes the resulting ffkitsaf: paths into the concat list file. The demuxer applies its own protocol whitelist, defaulting to file,crypto,data.

-safe 0 does not help: it permits absolute paths, this permits the scheme they carry. Two separate gates, and only one was open.

Why nothing caught it

The two halves never met:

  • Only STREAM_COPY feeds the demuxer a list file. REENCODE passes each input with its own -i, where the whitelist does not apply — so joining over SAF worked for mismatched clips.
  • Every join test passed Uri.fromFile, taking ConcatEngine's uri.path arm instead of the bridge. ConcatEngineTest.matchingClipsAreJoinedByStreamCopy exercised stream copy with a file: path and passed.

So the one broken combination — stream copy and a content URI — was the one no test produced and the only one a user can reach.

The #225 half

FFmpegKitConfig.getSafParameterForRead is on every real conversion and join and was on no passing test; only UnopenableUriTest touched it, against an authority that does not exist, which proves the error message rather than the bridge. ContentUriInputTest now drives both paths from a real content:// URI.

It uses a plain ContentProvider, because the documents provider cannot be reached — measured three ways on API 34:

approach result
DOCUMENTS_PROVIDER without MANAGE_DOCUMENTS refused at install: "Provider must be protected by MANAGE_DOCUMENTS"
Instrumentation.getContext() denied — instrumentation runs in the target app's process, so it carries the app's uid
adoptShellPermissionIdentity(MANAGE_DOCUMENTS) denied identically

Each denial names the only way in: "you obtain access using ACTION_OPEN_DOCUMENT or related APIs". That settles #226: its cheap headless half does not exist, so the work is one picker-driven item, not two.

The bridge needs no documents provider — it opens a descriptor through the resolver, so any readable content:// URI exercises it, and an ordinary provider may be exported unprotected. The class stays headless: no DocumentsUI, none of #190's flake.

Verification — local API 34 emulator

tests=66 failures=0 errors=0 skipped=3

Mutation — remove the -protocol_whitelist pair:

ContentUriInputTest > contentUriInputsJoinThroughTheSafBridge FAILED
  Protocol 'ffkitsaf' not on whitelist 'file,crypto,data'!

It reproduces the production failure verbatim, in that test and nothing else. FFmpegConcatCommandTest pins the flag on the JVM, next to the existing -safe 0 case.

🤖 Generated with Claude Code

Fixes #238. Closes #225. Settles the open question on #226. **Joining files picked through the system picker failed outright** whenever the strategy was stream copy — the matched-files case the UI advertises as *"joined without re-encoding — no quality loss"*. ``` [ffkitsaf @ ...] Protocol 'ffkitsaf' not on whitelist 'file,crypto,data'! Error opening input file .../joined_from_content.concat_list.txt ``` This was found by writing #225's test, not by looking for it. ## The defect `JoinScreen` picks with `OpenMultipleDocuments`, so real inputs are **always** `content://`. `ConcatEngine` maps each through `getSafParameterForRead`, and `FFmpegConcatCommand` writes the resulting `ffkitsaf:` paths into the concat list file. The demuxer applies its own protocol whitelist, defaulting to `file,crypto,data`. `-safe 0` does not help: it permits absolute **paths**, this permits the **scheme** they carry. Two separate gates, and only one was open. ## Why nothing caught it The two halves never met: - **Only `STREAM_COPY` feeds the demuxer a list file.** `REENCODE` passes each input with its own `-i`, where the whitelist does not apply — so joining over SAF worked for *mismatched* clips. - **Every join test passed `Uri.fromFile`**, taking `ConcatEngine`'s `uri.path` arm instead of the bridge. `ConcatEngineTest.matchingClipsAreJoinedByStreamCopy` exercised stream copy with a `file:` path and passed. So the one broken combination — stream copy **and** a content URI — was the one no test produced and the only one a user can reach. ## The #225 half `FFmpegKitConfig.getSafParameterForRead` is on every real conversion and join and was on no passing test; only `UnopenableUriTest` touched it, against an authority that does not exist, which proves the error message rather than the bridge. `ContentUriInputTest` now drives both paths from a real `content://` URI. **It uses a plain `ContentProvider`, because the documents provider cannot be reached** — measured three ways on API 34: | approach | result | |---|---| | `DOCUMENTS_PROVIDER` without `MANAGE_DOCUMENTS` | refused at install: *"Provider must be protected by MANAGE_DOCUMENTS"* | | `Instrumentation.getContext()` | denied — instrumentation runs in the **target app's process**, so it carries the app's uid | | `adoptShellPermissionIdentity(MANAGE_DOCUMENTS)` | denied identically | Each denial names the only way in: *"you obtain access using ACTION_OPEN_DOCUMENT or related APIs"*. **That settles #226**: its cheap headless half does not exist, so the work is one picker-driven item, not two. The bridge needs no documents provider — it opens a descriptor through the resolver, so any readable `content://` URI exercises it, and an ordinary provider may be exported unprotected. The class stays headless: no DocumentsUI, none of #190's flake. ## Verification — local API 34 emulator ``` tests=66 failures=0 errors=0 skipped=3 ``` Mutation — remove the `-protocol_whitelist` pair: ``` ContentUriInputTest > contentUriInputsJoinThroughTheSafBridge FAILED Protocol 'ffkitsaf' not on whitelist 'file,crypto,data'! ``` It reproduces the production failure verbatim, in that test and nothing else. `FFmpegConcatCommandTest` pins the flag on the JVM, next to the existing `-safe 0` case. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.