Skip to content

android: strip BOOT_COMPLETED receivers from merged manifest (#4832)#7158

Open
mdmohsin7 wants to merge 1 commit intomainfrom
caleb/android15-boot-receiver-strip
Open

android: strip BOOT_COMPLETED receivers from merged manifest (#4832)#7158
mdmohsin7 wants to merge 1 commit intomainfrom
caleb/android15-boot-receiver-strip

Conversation

@mdmohsin7
Copy link
Copy Markdown
Member

Summary

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).

Two of our plugins inject such receivers via their plugin manifests:

Plugin Receiver Service started FG type
flutter_foreground_task 9.1.0 RebootReceiver ForegroundService location
flutter_background_service_android 6.3.0 BootReceiver BackgroundService microphone

We never opt into autostart at runtime (autoRunOnBoot: false in app/lib/utils/audio/foreground.dart:129, autoStartOnBoot: false in app/lib/services/services.dart:148), so removing the receivers + the dead RECEIVE_BOOT_COMPLETED permission via tools: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 the RebootReceiver declaration.
  • ekasetiawans/flutter_background_service — issues #517 and #518 open since 2025-07-04. Latest 6.3.1 still ships the BootReceiver declaration.

Verification

Ran com.android.tools.build:manifest-merger 32.2.0 locally against the app's AndroidManifest.xml plus both plugin manifests, with --remove-tools-declarations to 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:

  • 0 occurrences of BOOT_COMPLETED
  • 0 occurrences of RECEIVE_BOOT_COMPLETED
  • All foreground service declarations preserved (location, microphone, connectedDevice)
  • RestartReceiver and WatchdogReceiver (no boot intent-filter) preserved
$ grep -cE 'BOOT_COMPLETED|RECEIVE_BOOT_COMPLETED' merged-fixed-clean.xml
0

Test plan

  • Codemagic dry-run on caleb/android15-boot-receiver-strip (android-internal-auto) — confirm gradle assembles with no manifest-merger conflict
  • Pull the produced APK from the dry-run artifacts, run aapt2 dump xmltree base.apk --file AndroidManifest.xml, confirm no BOOT_COMPLETED action and no RECEIVE_BOOT_COMPLETED permission
  • After Play Console internal track upload, confirm "Restricted foreground service types" warning no longer appears

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
@mdmohsin7 mdmohsin7 added android bug Something isn't working p2 Priority: Important (score 14-21) labels May 4, 2026
@mdmohsin7
Copy link
Copy Markdown
Member Author

@morpheus review — Approved

Standard Android manifest merger workaround. tools:node="remove" on the permission and both plugin-provided receivers, targeting their fully qualified class names. tools namespace already declared. autoRunOnBoot/autoStartOnBoot both false in Dart — runtime no-op. Clean single commit with issue reference.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

This PR uses AGP manifest-merger's tools:node="remove" directive to strip two plugin-injected BOOT_COMPLETED receivers (RebootReceiver from flutter_foreground_task and BootReceiver from flutter_background_service_android) and the associated RECEIVE_BOOT_COMPLETED permission from the merged manifest, unblocking Google Play's targetSdk-35 restricted-foreground-service-type policy check. The approach is correct and the PR includes solid local verification; the only risks are minor maintenance concerns noted inline.

Confidence Score: 4/5

Safe 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

Filename Overview
app/android/app/src/main/AndroidManifest.xml Adds tools:node="remove" stubs to suppress RECEIVE_BOOT_COMPLETED permission and two plugin boot receivers (RebootReceiver, BootReceiver) from the merged manifest; approach is technically correct for AGP manifest-merger but carries minor fragility risks.

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]
Loading

Reviews (1): Last reviewed commit: "android: strip BOOT_COMPLETED receivers ..." | Re-trigger Greptile

Comment on lines +93 to +98
<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" />
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.

P2 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.

Comment on lines +38 to +39
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"
tools:node="remove" />
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.

P2 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android bug Something isn't working p2 Priority: Important (score 14-21)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android 15: BOOT_COMPLETED broadcast restriction crashes foreground service startup

1 participant