android: strip BOOT_COMPLETED receivers from merged manifest (#4832)#7158
android: strip BOOT_COMPLETED receivers from merged manifest (#4832)#7158
Conversation
Google Play rejects targetSdk 35 builds when the merged AndroidManifest wires BOOT_COMPLETED receivers to restricted foreground service types (location, microphone, dataSync, camera, mediaPlayback, phoneCall, mediaProjection). flutter_foreground_task and flutter_background_service both inject such receivers via their plugin manifests and neither has shipped a fix (Dev-hwang/flutter_foreground_task#356, ekasetiawans/flutter_background_service#517 and #518 are open). We never opt into autostart (autoRunOnBoot/autoStartOnBoot are false), so removing the receivers and the dead RECEIVE_BOOT_COMPLETED permission via tools:node="remove" is a no-op at runtime and clears the Play scan. Verified by running com.android.tools.build:manifest-merger 32.2.0 locally against the app + plugin manifests with --remove-tools-declarations: the resulting merged manifest has no BOOT_COMPLETED action, no RECEIVE_BOOT_COMPLETED permission, and no boot receiver entries, while the location/microphone/connectedDevice foreground service declarations remain intact. Fixes #4832
|
@morpheus review — Approved ✅ Standard Android manifest merger workaround. |
Greptile SummaryThis PR uses AGP manifest-merger's Confidence Score: 4/5Safe to merge — the manifest-merger approach is correct and verified locally; only P2 maintenance notes remain. Single-file change with a well-understood, standard AGP pattern. The PR author verified the output with the manifest-merger tool directly. No P0/P1 issues found; remaining comments are P2 (future maintainability). app/android/app/src/main/AndroidManifest.xml — confirm the aapt2 dump test plan step is completed before ship to get on-device confirmation that no BOOT_COMPLETED entries remain in the final APK. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[AGP Manifest Merger] --> B[App AndroidManifest.xml]
A --> C[flutter_foreground_task manifest]
A --> D[flutter_background_service_android manifest]
B -->|tools:node=remove| E[RECEIVE_BOOT_COMPLETED permission ❌]
B -->|tools:node=remove| F[RebootReceiver stub ❌]
B -->|tools:node=remove| G[BootReceiver stub ❌]
C -->|suppressed by stub| F
D -->|suppressed by stub| G
A --> H[Merged AndroidManifest.xml]
H --> I[✅ ForegroundService location preserved]
H --> J[✅ BackgroundService microphone preserved]
H --> K[✅ OmiBleForegroundService connectedDevice preserved]
H --> L[✅ RestartReceiver / WatchdogReceiver preserved]
H --> M[❌ No BOOT_COMPLETED receivers]
H --> M2[❌ No RECEIVE_BOOT_COMPLETED permission]
Reviews (1): Last reviewed commit: "android: strip BOOT_COMPLETED receivers ..." | Re-trigger Greptile |
| <receiver | ||
| android:name="com.pravera.flutter_foreground_task.service.RebootReceiver" | ||
| tools:node="remove" /> | ||
| <receiver | ||
| android:name="id.flutter.flutter_background_service.BootReceiver" | ||
| tools:node="remove" /> |
There was a problem hiding this comment.
Silent failure if boot-autostart is ever re-enabled
These tools:node="remove" stubs permanently strip the boot receivers from the merged manifest. If a future developer sets autoRunOnBoot: true in foreground.dart or autoStartOnBoot: true in services.dart, the receivers will already be gone and the auto-start will silently do nothing on reboot — no crash, no log, just dead configuration. A short inline comment explaining that these override plugin-injected boot receivers (and referencing the disabled autoRunOnBoot/autoStartOnBoot options) would prevent this from becoming a mystery bug.
| <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" | ||
| tools:node="remove" /> |
There was a problem hiding this comment.
Plugin library manifests may re-contribute the permission
tools:node="remove" on the app-level <uses-permission> removes the app's own declaration and, in the merger's conflict-resolution pass, prevents lower-priority manifests from winning for the same key. The PR's own verification run (grep -c RECEIVE_BOOT_COMPLETED merged-fixed-clean.xml → 0) confirms this works end-to-end. No action needed — this note is purely for reviewer awareness, and the test plan steps with aapt2 dump on the produced APK will provide definitive confirmation.
Summary
Google Play rejects targetSdk-35 builds when the merged AndroidManifest wires
BOOT_COMPLETEDreceivers to restricted foreground service types (location,microphone,dataSync,camera,mediaPlayback,phoneCall,mediaProjection).Two of our plugins inject such receivers via their plugin manifests:
flutter_foreground_task9.1.0RebootReceiverForegroundServicelocationflutter_background_service_android6.3.0BootReceiverBackgroundServicemicrophoneWe never opt into autostart at runtime (
autoRunOnBoot: falseinapp/lib/utils/audio/foreground.dart:129,autoStartOnBoot: falseinapp/lib/services/services.dart:148), so removing the receivers + the deadRECEIVE_BOOT_COMPLETEDpermission viatools:node="remove"is a no-op at runtime and clears the Play scan.Why not upgrade the plugins
Both upstreams have unfixed open issues and no shipped workaround:
Dev-hwang/flutter_foreground_task— issue #356 open since 2025-07-31, closed #335 without a fix. Latest version 9.2.2 still ships theRebootReceiverdeclaration.ekasetiawans/flutter_background_service— issues #517 and #518 open since 2025-07-04. Latest 6.3.1 still ships theBootReceiverdeclaration.Verification
Ran
com.android.tools.build:manifest-merger32.2.0 locally against the app'sAndroidManifest.xmlplus both plugin manifests, with--remove-tools-declarationsto mirror what AGP does:Baseline (current
main) merged manifest contains:<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><receiver android:name="com.pravera.flutter_foreground_task.service.RebootReceiver">with<action android:name="android.intent.action.BOOT_COMPLETED"/><receiver android:name="id.flutter.flutter_background_service.BootReceiver">with<action android:name="android.intent.action.BOOT_COMPLETED"/>With this PR applied the merged manifest contains:
BOOT_COMPLETEDRECEIVE_BOOT_COMPLETEDlocation,microphone,connectedDevice)RestartReceiverandWatchdogReceiver(no boot intent-filter) preservedTest plan
caleb/android15-boot-receiver-strip(android-internal-auto) — confirm gradle assembles with no manifest-merger conflictaapt2 dump xmltree base.apk --file AndroidManifest.xml, confirm noBOOT_COMPLETEDaction and noRECEIVE_BOOT_COMPLETEDpermission