Skip to content

Stop declaring photo/video permissions for stream media playback (#5507) - #5509

Merged
shai-almog merged 2 commits into
masterfrom
fix/media-permission-overload-detection
Aug 2, 2026
Merged

Stop declaring photo/video permissions for stream media playback (#5507)#5509
shai-almog merged 2 commits into
masterfrom
fix/media-permission-overload-detection

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Fixes #5507.

ddyer0 reported READ_MEDIA_IMAGES and READ_MEDIA_VIDEO appearing in a manifest without asking for them. The app plays a locally derived stream:

media = MediaManager.createMedia(instream, format, complete);

Those two permissions together put an app under Google Play's Photo and Video Permissions policy, so the report is a declaration form and a compliance deadline for a capability the app does not have.

Two causes

The scanner could not tell the overloads apart. Executor.ClassScanner reported a method name and nothing else, so createMedia(String,boolean) and createMedia(InputStream,String) looked identical. They are not interchangeable:

  • the URI overloads hand a string to the platform, which may resolve it against the MediaStore — AndroidImplementation:4402 really does request PERMISSION_READ_VIDEO / PERMISSION_READ_AUDIO for those;
  • the InputStream overloads cannot reach shared media at all. AndroidImplementation:4514 either plays an already-open FileInputStream's descriptor or copies the stream into File.createTempFile in app-private storage, and checks no permission on either path.

ClassScanner gains usesClassMethodWithDescriptor, fed from both visitMethodInsn overloads and from the Handle of an invokedynamic (so MediaManager::createMedia is selectable too), and the createMedia rule moves onto it.

READ_MEDIA_IMAGES was emitted for any detected playback. Nothing in the Android port can use it. The only runtime request site in the whole port is AndroidImplementation:4402, which passes PERMISSION_READ_VIDEO or PERMISSION_READ_AUDIO; no caller anywhere passes PERMISSION_READ_IMAGES. It is now declared only when an app asks outright with android.requestReadMediaPermissions=true.

Deliberate choices

  • Video and audio stay paired. The runtime picks between them on the call's isVideo argument, which this scan does not read.
  • An unrecognized descriptor still counts as the URI overload. Over-declaring costs a permission; under-declaring costs a SecurityException on a user's device.
  • android.requestReadMediaPermissions is unchanged and still declares all three, so nobody relying on that hint sees a difference.

Also here

android.blockReadMediaPermissions and android.requestReadMediaPermissions were in neither build hint table nor the permission-mapping list in the developer guide. Both are documented now.

Companion

A matching PR against BuildDaemon carries the same fix to the cloud builder, where the same two files are physically duplicated.

Test plan

  • MediaPlaybackPermissionScanTest — 12 new tests: the scanner reports a descriptor for a direct call and a method reference; each overload is classified; the recorder is excluded; a null descriptor is assumed permission-worthy; and the emitted permission set is asserted for playback, the opt-in hint, the block hint and pre-API-33.
  • Full module suite: 419 passing.
  • SpotBugs on codenameone-maven-plugin: 0 findings.

🤖 Generated with Claude Code

An app that plays a bundled sound with
MediaManager.createMedia(InputStream, String, Runnable) was built with
READ_MEDIA_IMAGES and READ_MEDIA_VIDEO in its manifest. Those two
together put the app under Google Play's Photo and Video Permissions
policy, so its author gets a declaration form and a compliance deadline
for a capability the app does not have.

Two separate causes.

The scanner reported only a method name, so the createMedia overloads
were indistinguishable. They are not interchangeable: the URI overloads
hand a string to the platform, which may resolve it against the
MediaStore, while the InputStream overloads either play an already-open
FileInputStream's descriptor or copy the stream into a temp file in
app-private storage -- AndroidImplementation asks for no permission on
either path. ClassScanner gains usesClassMethodWithDescriptor, fed from
both visitMethodInsn overloads and from the Handle of an invokedynamic,
and the createMedia rule moves onto it.

READ_MEDIA_IMAGES was also emitted for any detected playback. Nothing
in the Android port can use it: the one runtime request site passes
PERMISSION_READ_VIDEO or PERMISSION_READ_AUDIO, and no caller anywhere
passes PERMISSION_READ_IMAGES. It is now declared only when an app asks
outright with android.requestReadMediaPermissions.

Video and audio stay paired, since the runtime chooses between them on
the isVideo argument, which this scan does not read. An unrecognized
descriptor still counts as the URI overload: over-declaring costs a
permission, under-declaring costs a SecurityException on a device.

Also documents android.blockReadMediaPermissions and
android.requestReadMediaPermissions in the developer guide, neither of
which was listed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 02:13

Copilot AI 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.

Pull request overview

This PR fixes Android manifest permission inference in the Codename One Maven plugin so that apps that play media via MediaManager.createMedia(InputStream, ...) (stream overloads) no longer automatically declare READ_MEDIA_VIDEO / READ_MEDIA_IMAGES (and thus don’t get pulled into Google Play’s “Photo and Video Permissions” policy workflow). It does this by teaching the bytecode scanner to distinguish overloads using method descriptors, and by tightening the mapping from detected playback to emitted READ_MEDIA_* permissions.

Changes:

  • Extend Executor.ClassScanner to report method calls with descriptors (including invokedynamic handles), enabling overload-specific detection.
  • Update Android permission inference to only treat URI-based createMedia*(String, ...) overloads as requiring READ_MEDIA_VIDEO/READ_MEDIA_AUDIO, and to emit READ_MEDIA_IMAGES only when explicitly requested via build hint.
  • Add a focused JUnit test suite covering overload classification, method references, and emitted permission sets; document the new build hints in the developer guide.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MediaPlaybackPermissionScanTest.java Adds tests validating descriptor-aware scanning and correct READ_MEDIA_* emission rules.
maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/Executor.java Enhances the class scanner to report method descriptors for calls and method references (invokedynamic).
maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/AndroidGradleBuilder.java Refines playback detection to be overload-aware and centralizes READ_MEDIA_* permission emission logic.
docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc Documents android.blockReadMediaPermissions / android.requestReadMediaPermissions and clarifies when READ_MEDIA_* are added.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 02:27

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 7.85% (7605/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.72% (39610/513127), branch 2.80% (1363/48627), complexity 3.15% (1646/52177), method 4.87% (1345/27595), class 9.98% (367/3679)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 7.85% (7605/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.72% (39610/513127), branch 2.80% (1363/48627), complexity 3.15% (1646/52177), method 4.87% (1345/27595), class 9.98% (367/3679)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 288ms / native 109ms = 2.6x speedup
SIMD float-mul (64K x300) java 271ms / native 84ms = 3.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 79.000 ms
Base64 CN1 decode 87.000 ms
Base64 native encode 349.000 ms
Base64 encode ratio (CN1/native) 0.226x (77.4% faster)
Base64 native decode 299.000 ms
Base64 decode ratio (CN1/native) 0.291x (70.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 191 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 158.000 ms
Base64 CN1 decode 92.000 ms
Base64 native encode 471.000 ms
Base64 encode ratio (CN1/native) 0.335x (66.5% faster)
Base64 native decode 247.000 ms
Base64 decode ratio (CN1/native) 0.372x (62.8% faster)
Base64 SIMD encode 48.000 ms
Base64 encode ratio (SIMD/CN1) 0.304x (69.6% faster)
Base64 SIMD decode 59.000 ms
Base64 decode ratio (SIMD/CN1) 0.641x (35.9% faster)
Base64 encode ratio (SIMD/native) 0.102x (89.8% faster)
Base64 decode ratio (SIMD/native) 0.239x (76.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.167x (83.3% faster)
Image applyMask (SIMD off) 50.000 ms
Image applyMask (SIMD on) 38.000 ms
Image applyMask ratio (SIMD on/off) 0.760x (24.0% faster)
Image modifyAlpha (SIMD off) 45.000 ms
Image modifyAlpha (SIMD on) 97.000 ms
Image modifyAlpha ratio (SIMD on/off) 2.156x (115.6% slower)
Image modifyAlpha removeColor (SIMD off) 132.000 ms
Image modifyAlpha removeColor (SIMD on) 59.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.447x (55.3% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 545 seconds

Build and Run Timing

Metric Duration
Simulator Boot 96000 ms
Simulator Boot (Run) 1000 ms
App Install 18000 ms
App Launch 11000 ms
Test Execution 495000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 70ms / native 4ms = 17.5x speedup
SIMD float-mul (64K x300) java 78ms / native 2ms = 39.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 254.000 ms
Base64 CN1 decode 271.000 ms
Base64 native encode 1817.000 ms
Base64 encode ratio (CN1/native) 0.140x (86.0% faster)
Base64 native decode 1887.000 ms
Base64 decode ratio (CN1/native) 0.144x (85.6% faster)
Base64 SIMD encode 276.000 ms
Base64 encode ratio (SIMD/CN1) 1.087x (8.7% slower)
Base64 SIMD decode 163.000 ms
Base64 decode ratio (SIMD/CN1) 0.601x (39.9% faster)
Base64 encode ratio (SIMD/native) 0.152x (84.8% faster)
Base64 decode ratio (SIMD/native) 0.086x (91.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.400x (60.0% faster)
Image applyMask (SIMD off) 61.000 ms
Image applyMask (SIMD on) 41.000 ms
Image applyMask ratio (SIMD on/off) 0.672x (32.8% faster)
Image modifyAlpha (SIMD off) 102.000 ms
Image modifyAlpha (SIMD on) 148.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.451x (45.1% slower)
Image modifyAlpha removeColor (SIMD off) 119.000 ms
Image modifyAlpha removeColor (SIMD on) 248.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 2.084x (108.4% slower)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 326 seconds

Build and Run Timing

Metric Duration
Simulator Boot 65000 ms
Simulator Boot (Run) 1000 ms
App Install 12000 ms
App Launch 3000 ms
Test Execution 405000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 4ms = 13.5x speedup
SIMD float-mul (64K x300) java 55ms / native 2ms = 27.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 165.000 ms
Base64 CN1 decode 101.000 ms
Base64 native encode 608.000 ms
Base64 encode ratio (CN1/native) 0.271x (72.9% faster)
Base64 native decode 649.000 ms
Base64 decode ratio (CN1/native) 0.156x (84.4% faster)
Base64 SIMD encode 49.000 ms
Base64 encode ratio (SIMD/CN1) 0.297x (70.3% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.446x (55.4% faster)
Base64 encode ratio (SIMD/native) 0.081x (91.9% faster)
Base64 decode ratio (SIMD/native) 0.069x (93.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 25.000 ms
Image createMask (SIMD on) 14.000 ms
Image createMask ratio (SIMD on/off) 0.560x (44.0% faster)
Image applyMask (SIMD off) 249.000 ms
Image applyMask (SIMD on) 307.000 ms
Image applyMask ratio (SIMD on/off) 1.233x (23.3% slower)
Image modifyAlpha (SIMD off) 207.000 ms
Image modifyAlpha (SIMD on) 106.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.512x (48.8% faster)
Image modifyAlpha removeColor (SIMD off) 132.000 ms
Image modifyAlpha removeColor (SIMD on) 108.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.818x (18.2% faster)

@shai-almog
shai-almog merged commit c3dabfd into master Aug 2, 2026
42 checks passed
@shai-almog
shai-almog deleted the fix/media-permission-overload-detection branch August 2, 2026 09:48
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.

[Compatibility] new android compatibility

2 participants