Skip to content

feat(android): expose runtime POST_NOTIFICATIONS permission request for the FGS#109

Open
gmaclennan wants to merge 2 commits into
mainfrom
feat/notification-permission
Open

feat(android): expose runtime POST_NOTIFICATIONS permission request for the FGS#109
gmaclennan wants to merge 2 commits into
mainfrom
feat/notification-permission

Conversation

@gmaclennan

Copy link
Copy Markdown
Member

Closes #97

Summary

On Android 13+ (API 33) the foreground-service (FGS) notification is runtime-gated behind POST_NOTIFICATIONS. Without the grant the system suppresses the notification, which lets it deprioritise or kill the service. Below API 33 and on iOS the permission is auto-granted.

Per the maintainer's design decision, the module exposes the capability and the host app decides when to call it — there is no auto-prompt.

What changed

  • JS API (src/): adds getNotificationPermissionsAsync() and requestNotificationPermissionsAsync(), both returning an expo-style PermissionResponse ({ status, granted, canAskAgain, expires }). Exported from src/index.ts, typed in src/ComapeoCore.types.ts, wired through src/ComapeoCoreModule.ts. On iOS and Android < 33 they resolve as granted (no-op) so host code never has to branch on platform.
  • Android native: routed through expo-modules-core's permissions plumbing — appContext.permissions + Permissions.getPermissionsWithPermissionsManager / askForPermissionsWithPermissionsManager. This reuses expo's PermissionsService, which owns onRequestPermissionsResult and produces the {status, granted, canAskAgain, expires} response, including the granted / denied / "don't ask again" (canAskAgain=false via shouldShowRequestPermissionRationale) cases. No hand-rolled ActivityCompat path was needed because PermissionsService is registered as a default internal module of expo-modules-core, so appContext.permissions is always available.
  • Service guard (ComapeoCoreService.kt): before startForeground on API 33+, checks ContextCompat.checkSelfPermission(POST_NOTIFICATIONS) and logs a warning when missing. Wraps startForeground in a try/catch (SecurityException) so a missing grant degrades gracefully — logged to diagnostics, service keeps running deprioritised, no crash.
  • iOS (ComapeoCoreModule.swift): both methods resolve a granted response (no FGS / no matching runtime gate on iOS).
  • Docs (docs/ForegroundService.md): documents the contract — module exposes the check/request methods, host app calls them (typically around starting the service), including rationale + settings deep-link UX (tracked in UX for notification permission (rationale + denied/settings flow) #100). Written for a JS/Node audience.

Acceptance criteria (#97)

  • Host can trigger the runtime request (requestNotificationPermissionsAsync())
  • already-granted / denied / "don't ask again" handled (expo PermissionsService maps these into status + canAskAgain)
  • Contract documented (module exposes, host requests)
  • Correct on < API 33 (auto-granted: checkSelfPermission reports the manifest permission granted, so the methods resolve granted without a dialog)
  • Correct when denied (status denied, granted=false; canAskAgain=false once the user picks "Don't ask again")
  • Service start does not crash when the permission is missing (graceful degrade + log)

Checks run

  • npm run lint — pass
  • npx tsc --noEmit — pass
  • npm test (jest) — pass, 19/19 (4 new in src/__tests__/notification-permissions.test.js + 15 pre-existing)

Notes on the test environment: the fresh worktree needed src/version.ts generated (node scripts/write-version.mjs, normally part of prepare) and a top-level babel-preset-expo (a transitive expo dep that wasn't hoisted) before jest would run. Neither is committed.

Not run locally (relies on CI)

  • Kotlin compilation / Android instrumentation: there is no generated example/android app or gradlew in this worktree, so the library module can't be compiled or run here. The Kotlin symbols used were verified against the installed expo-modules-core@56 source (AppContext.permissions: Permissions?, the Permissions.*WithPermissionsManager overloads accepting expo.modules.kotlin.Promise, PermissionsService default registration). The runtime permission-dialog and denial flows require a physical device / emulator on API 33+.
  • iOS build: not compiled here; the Swift change is a trivial constant-returning AsyncFunction pair.

🤖 Generated with Claude Code

gmaclennan and others added 2 commits June 22, 2026 12:09
…or the FGS

On Android 13+ (API 33) the foreground-service notification is runtime-gated
behind POST_NOTIFICATIONS; without the grant the system suppresses the
notification and may deprioritise or kill the service. Below API 33 and on
iOS the permission is auto-granted.

The module now exposes expo-style check/request methods and leaves the
decision of when to call them to the host app — it never auto-prompts:

- JS: `getNotificationPermissionsAsync()` / `requestNotificationPermissionsAsync()`
  return a `PermissionResponse` ({ status, granted, canAskAgain, expires }).
  On iOS and Android < 33 they resolve `granted` so host code is
  cross-platform safe.
- Android: routed through expo-modules-core's permissions plumbing
  (`appContext.permissions` + `Permissions.{get,ask}ForPermissionsWithPermissionsManager`),
  which handles onRequestPermissionsResult and the granted / denied /
  "don't ask again" (canAskAgain) cases.
- Service guard: `ComapeoCoreService` checks the grant before
  `startForeground` on API 33+ and catches the SecurityException path so a
  missing grant degrades gracefully (logged, never crashes) instead of
  taking the process down.

Documents the contract in docs/ForegroundService.md (module exposes, host
requests; settings deep-link UX tracked in #100). Adds JS-level tests for
the new API surface; native behaviour requires a device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gmaclennan gmaclennan marked this pull request as ready for review June 22, 2026 12:25
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.

Request POST_NOTIFICATIONS permission at runtime for the foreground service

1 participant