Closes#194. Touches production, unlike the four test-only PRs in this wave.
This is not a third run of the #86/#133 spike
probe() was 20 never-executed lines, the biggest single block on the report. It has been looked at twice and left out twice, and both closes were right about what they closed:
#133 re-checked with ShadowMediaCodecList in hand and still declined, because MediaCodecInfoBuilder has no setIsAlias and no setCanonicalName — "so the alias skip and the canonical-name dedup, the two things the class's KDoc calls out as easy to get wrong, are not reachable through it."
That objection is about the shadow. It does not apply to a function taking its own entry type, which #133 did not evaluate. capabilitiesFrom(enumerate: () -> Sequence<CodecEntry>) holds every rule; the edge keeps only the mapping from MediaCodecList onto CodecEntry.
Why the parameter is a Sequence
runCatching has always wrapped the iteration, so a MediaCodecInfo whose properties throw partway leaves the codecs already read in place. A List parameter would move that throw outside the loop and turn a partial answer into an empty one — a behaviour change smuggled in as a refactor. There is now a test for the partial case, and swapping the Sequence for an eager toList() reddens it.
The one behaviour change, which is a log line
The fallback said "Codec enumeration failed; assuming permissive" and returned empty sets. But "video/avc" in emptySet() is false, so canEncode and canDecode answer no to everything and every job routes to FFmpeg. That is the restrictive answer — and the right one, since FFmpeg does whatever Media3 does, only slower.
Per the decision on #194: the code stays, the message changes. The class KDoc gains a third bullet saying so, since it implied the opposite.
One test was wrong, and the mutation is what said so
The alias case originally listed the alias after the codec it aliases, and passed with the skip deleted — canonicalName is shared, so the dedup catches the second entry either way. The two rules overlap, and a fixture that does not separate them tests neither.
Order separates them. An alias arriving first claims the canonical name in seen, has its own supportedTypes credited, and the real codec is then dropped by the dedup. That is now the test — and it also pins what the rule is worth: with a Set accumulator an alias declaring the same types changes nothing, so the skip earns its place only when the two disagree.
Acceptance: seven mutations, each run and restored
mutation
red
drop !isSoftwareOnly from the encoder predicate
1
remove the alias skip
1 — 0 before the fixture was fixed
remove the canonical-name dedup
1
remove the video/ prefix filter
1
apply the hardware predicate to decoders too
1
make the failure fallback permissive
2
eager toList() instead of the lazy Sequence
1
Each reddens the test that owns it, so no test is riding on another's rule.
Verification
assembleDebug + testDebugUnitTest (full suite) + compileDebugAndroidTestKotlin + ktlintCheck + detekt + lintDebug — green. Two formatting complaints came up and were fixed with ktlintFormat, not by hand.
Closes #194. **Touches production**, unlike the four test-only PRs in this wave.
## This is not a third run of the #86/#133 spike
`probe()` was 20 never-executed lines, the biggest single block on the report. It has been looked at twice and left out twice, and **both closes were right about what they closed**:
- **#86** ruled it device-bound.
- **#133** re-checked with `ShadowMediaCodecList` in hand and still declined, because `MediaCodecInfoBuilder` has no `setIsAlias` and no `setCanonicalName` — *"so the alias skip and the canonical-name dedup, the two things the class's KDoc calls out as easy to get wrong, are not reachable through it."*
That objection is about **the shadow**. It does not apply to a function taking its own entry type, which #133 did not evaluate. `capabilitiesFrom(enumerate: () -> Sequence<CodecEntry>)` holds every rule; the edge keeps only the mapping from `MediaCodecList` onto `CodecEntry`.
## Why the parameter is a `Sequence`
`runCatching` has always wrapped the **iteration**, so a `MediaCodecInfo` whose properties throw partway leaves the codecs already read in place. A `List` parameter would move that throw outside the loop and turn a partial answer into an empty one — a behaviour change smuggled in as a refactor. There is now a test for the partial case, and swapping the `Sequence` for an eager `toList()` reddens it.
## The one behaviour change, which is a log line
The fallback said *"Codec enumeration failed; assuming permissive"* and returned empty sets. But `"video/avc" in emptySet()` is `false`, so `canEncode` and `canDecode` answer **no** to everything and every job routes to FFmpeg. That is the *restrictive* answer — and the right one, since FFmpeg does whatever Media3 does, only slower.
Per the decision on #194: **the code stays, the message changes.** The class KDoc gains a third bullet saying so, since it implied the opposite.
## One test was wrong, and the mutation is what said so
The alias case originally listed the alias **after** the codec it aliases, and **passed with the skip deleted** — `canonicalName` is shared, so the dedup catches the second entry either way. The two rules overlap, and a fixture that does not separate them tests neither.
**Order separates them.** An alias arriving *first* claims the canonical name in `seen`, has its own `supportedTypes` credited, and the real codec is then dropped by the dedup. That is now the test — and it also pins what the rule is worth: with a `Set` accumulator an alias declaring the *same* types changes nothing, so the skip earns its place only when the two disagree.
## Acceptance: seven mutations, each run and restored
| mutation | red |
|---|---|
| drop `!isSoftwareOnly` from the encoder predicate | 1 |
| remove the alias skip | 1 — **0 before the fixture was fixed** |
| remove the canonical-name dedup | 1 |
| remove the `video/` prefix filter | 1 |
| apply the hardware predicate to decoders too | 1 |
| make the failure fallback permissive | 2 |
| eager `toList()` instead of the lazy `Sequence` | 1 |
Each reddens the test that owns it, so no test is riding on another's rule.
## Verification
`assembleDebug` + `testDebugUnitTest` (full suite) + `compileDebugAndroidTestKotlin` + `ktlintCheck` + `detekt` + `lintDebug` — green. Two formatting complaints came up and were fixed with `ktlintFormat`, not by hand.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #194. Touches production, unlike the four test-only PRs in this wave.
This is not a third run of the #86/#133 spike
probe()was 20 never-executed lines, the biggest single block on the report. It has been looked at twice and left out twice, and both closes were right about what they closed:ShadowMediaCodecListin hand and still declined, becauseMediaCodecInfoBuilderhas nosetIsAliasand nosetCanonicalName— "so the alias skip and the canonical-name dedup, the two things the class's KDoc calls out as easy to get wrong, are not reachable through it."That objection is about the shadow. It does not apply to a function taking its own entry type, which #133 did not evaluate.
capabilitiesFrom(enumerate: () -> Sequence<CodecEntry>)holds every rule; the edge keeps only the mapping fromMediaCodecListontoCodecEntry.Why the parameter is a
SequencerunCatchinghas always wrapped the iteration, so aMediaCodecInfowhose properties throw partway leaves the codecs already read in place. AListparameter would move that throw outside the loop and turn a partial answer into an empty one — a behaviour change smuggled in as a refactor. There is now a test for the partial case, and swapping theSequencefor an eagertoList()reddens it.The one behaviour change, which is a log line
The fallback said "Codec enumeration failed; assuming permissive" and returned empty sets. But
"video/avc" in emptySet()isfalse, socanEncodeandcanDecodeanswer no to everything and every job routes to FFmpeg. That is the restrictive answer — and the right one, since FFmpeg does whatever Media3 does, only slower.Per the decision on #194: the code stays, the message changes. The class KDoc gains a third bullet saying so, since it implied the opposite.
One test was wrong, and the mutation is what said so
The alias case originally listed the alias after the codec it aliases, and passed with the skip deleted —
canonicalNameis shared, so the dedup catches the second entry either way. The two rules overlap, and a fixture that does not separate them tests neither.Order separates them. An alias arriving first claims the canonical name in
seen, has its ownsupportedTypescredited, and the real codec is then dropped by the dedup. That is now the test — and it also pins what the rule is worth: with aSetaccumulator an alias declaring the same types changes nothing, so the skip earns its place only when the two disagree.Acceptance: seven mutations, each run and restored
!isSoftwareOnlyfrom the encoder predicatevideo/prefix filtertoList()instead of the lazySequenceEach reddens the test that owns it, so no test is riding on another's rule.
Verification
assembleDebug+testDebugUnitTest(full suite) +compileDebugAndroidTestKotlin+ktlintCheck+detekt+lintDebug— green. Two formatting complaints came up and were fixed withktlintFormat, not by hand.🤖 Generated with Claude Code