Skip to content

feat(android): foregroundServiceType and endCallsOnHostDestroy options - #905

Merged
sboily merged 2 commits into
masterfrom
feat/android-call-lifetime-options
Oct 17, 2026
Merged

sboily merged 2 commits into
masterfrom
feat/android-call-lifetime-options

Conversation

@sboily

@sboily sboily commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Two Android options for apps whose calls aren't tied to the React activity. Both come from the patch Luge Mobile carries on 4.4.0. Without them, nothing changes.

foregroundService.foregroundServiceType

The value is passed to startForeground on Android 10 and later, as a mask of ServiceInfo.FOREGROUND_SERVICE_TYPE_* values. Without a type, Android starts the service with every type its manifest entry declares. An app that declares phoneCall|microphone therefore couldn't ring with phoneCall alone. Android 14+ refuses a microphone service without RECORD_AUDIO (SecurityException: Starting FGS with type microphone ... requires permissions), and doesn't give the microphone to a service started from the background.

CallKeep starts the service once, when the call is created, and keeps the given types for the whole call. With 4 alone, adding microphone after the answer is up to the app's native code: it calls startForeground again on VoiceConnectionService.currentConnectionService. docs/setup.md says so. Luge rings with 4 and restarts the service with phoneCall|microphone when the call is answered.

Each type must be declared on the service in the manifest.

startForeground's first argument is the notification's id, but it was named FOREGROUND_SERVICE_TYPE_MICROPHONE, which reads as a type next to a real one. Constants.FOREGROUND_SERVICE_NOTIFICATION_ID now names it. The old constant keeps the same value for apps that use it.

endCallsOnHostDestroy: false

onHostDestroy ends every call and kills the process. That is right when the activity is the call's only owner. It also ends a call that something else still holds, such as native code or a call screen in its own task. With false, the calls and the process stay, and ending them is up to the app. The default stays true.

Tests

New tests/android/CallKeepCallLifetimeOptionsTest.kt, run in the example app (React Native 0.87.1) on a Galaxy S24, Android 16:

master this branch
theForegroundServiceStartsWithTheGivenType fails: expected:<4> but was:<132> (the manifest's phoneCall|microphone) OK
theCallOutlivesTheActivityWhenAsked process killed by onHostDestroy (SIGKILL, reported as a crash) OK

The type test asserts that the host declares phoneCall|microphone on the service. With phoneCall alone, master also starts the service as phoneCall, and the test would prove nothing.

./gradlew :react-native-callkeep:lintDebug reports no errors, and npm run check passes. index.d.ts, docs/setup.md and CHANGELOG.md are updated.

The tests live in a new file so that this branch doesn't conflict with #904, which changes CallKeepForegroundServiceTest.kt.

🤖 Generated with Claude Code

Two options for apps whose calls are not tied to the React activity.

foregroundService.foregroundServiceType is passed to startForeground
(Android 10 and later). Without a type, Android starts the service with
every type its manifest entry declares, so an app that declares
phoneCall|microphone could not ring with phoneCall alone and add the
microphone once the call is answered. Without the option, nothing
changes.

endCallsOnHostDestroy: false makes onHostDestroy leave the calls and the
process. By default it still ends every call and kills the process,
which is right when the activity is the call's only owner, but ends a
call that native code (a service, a call screen in its own task) still
holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/setup.md and the changelog say that CallKeep starts the service
  once and keeps the given types for the whole call: with phoneCall
  alone, adding microphone after the answer is up to the app's native
  code, with startForeground on currentConnectionService.
- The type test asserts that the host declares phoneCall|microphone.
  With phoneCall alone, master also starts the service as phoneCall and
  the test passed without proving anything; its comment said the host
  declares phoneCall.
- startForeground's first argument is the notification's id, but it was
  named FOREGROUND_SERVICE_TYPE_MICROPHONE, which reads as a type next
  to a real one. Constants.FOREGROUND_SERVICE_NOTIFICATION_ID names it;
  the old constant keeps the same value for apps that use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sboily
sboily merged commit ccfae27 into master Oct 17, 2026
4 checks passed
@sboily
sboily deleted the feat/android-call-lifetime-options branch October 17, 2026 21:21
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.

1 participant