Stop declaring photo/video permissions for stream media playback (#5507) - #5509
Conversation
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>
There was a problem hiding this comment.
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.ClassScannerto report method calls with descriptors (includinginvokedynamichandles), enabling overload-specific detection. - Update Android permission inference to only treat URI-based
createMedia*(String, ...)overloads as requiringREAD_MEDIA_VIDEO/READ_MEDIA_AUDIO, and to emitREAD_MEDIA_IMAGESonly 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.
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloudflare Preview
|
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 181 screenshots: 181 matched. |
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 144 screenshots: 144 matched. |
|
Compared 217 screenshots: 217 matched. |
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Fixes #5507.
ddyer0reportedREAD_MEDIA_IMAGESandREAD_MEDIA_VIDEOappearing in a manifest without asking for them. The app plays a locally derived stream: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.ClassScannerreported a method name and nothing else, socreateMedia(String,boolean)andcreateMedia(InputStream,String)looked identical. They are not interchangeable:AndroidImplementation:4402really does requestPERMISSION_READ_VIDEO/PERMISSION_READ_AUDIOfor those;AndroidImplementation:4514either plays an already-openFileInputStream's descriptor or copies the stream intoFile.createTempFilein app-private storage, and checks no permission on either path.ClassScannergainsusesClassMethodWithDescriptor, fed from bothvisitMethodInsnoverloads and from theHandleof aninvokedynamic(soMediaManager::createMediais selectable too), and thecreateMediarule moves onto it.READ_MEDIA_IMAGESwas emitted for any detected playback. Nothing in the Android port can use it. The only runtime request site in the whole port isAndroidImplementation:4402, which passesPERMISSION_READ_VIDEOorPERMISSION_READ_AUDIO; no caller anywhere passesPERMISSION_READ_IMAGES. It is now declared only when an app asks outright withandroid.requestReadMediaPermissions=true.Deliberate choices
isVideoargument, which this scan does not read.SecurityExceptionon a user's device.android.requestReadMediaPermissionsis unchanged and still declares all three, so nobody relying on that hint sees a difference.Also here
android.blockReadMediaPermissionsandandroid.requestReadMediaPermissionswere 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.codenameone-maven-plugin: 0 findings.🤖 Generated with Claude Code