Repository navigation
fix(android): the foreground service keeps an app's channel and starts without the module - #904
Merged
Merged
Conversation
…s without the module startForegroundService created the channel named by foregroundService.channelId every time, with IMPORTANCE_NONE. When that channel already exists because the app created it (an app can pass its own incoming-call channel), Android lets the app lower its importance: on a Galaxy S24 with Android 16 it went from high to low, and the app's notifications in it stopped alerting. An existing channel is now left as it is. It also read RNCallKeepModule.instance without a check. Telecom can create a connection in a process where the module was never created, for instance a call placed from the Phone app while the app isn't running (makeOutgoingCall starts the service), and the app crashed with a NullPointerException. Without the module, the notification opens no activity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
…channel takes its name, per review The foreground service read its settings with getForegroundSettings(null), which only works once the module or another caller has loaded them. In a process without the module, a call placed from the Phone app found no settings and started no foreground service (no crash, as the first commit wrongly said), while an incoming call, whose onCreateIncomingConnection loads them with getSettings(this), went on to the NullPointerException. The service now reads them with its own context, so both get their foreground service, and the null check on the module covers both. An existing channel still keeps its importance and sound, but takes channelName again, as creating it did: a renamed or translated channel name was no longer applied. The tests follow: the channel test checks the name, and the test without the module also clears the loaded settings, as in a new process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes in
VoiceConnectionService.startForegroundService(). Both come from the patch Luge Mobile carries on 4.4.0.An app's own channel lost its importance
foregroundService.channelIdcan name a channel the app already created, such as the one its incoming calls ring in.startForegroundService()created it again withIMPORTANCE_NONE, and Android lets an app lower the importance of its own channel as long as the user hasn't changed it. On a Galaxy S24 with Android 16 it went from high (4) to low (2), and the app's notifications in that channel stopped alerting.An existing channel now keeps its importance and sound, and still takes
channelName, so a renamed or translated name is applied as before. CallKeep still creates the channel when it doesn't exist.docs/setup.mdsays so forchannelId.A call in a process without the module
The service read its settings with
getForegroundSettings(null), which returns them only once the module, or another caller with a context, has loaded them. In a process where the module doesn't exist:makeOutgoingCallstarted no foreground service (no crash);onCreateIncomingConnection(getSettings(this)), thenstartForegroundService()readRNCallKeepModule.instancewithout a check:NullPointerException. That happens for a call added from native code (or withsetupNative, feat: show an incoming call from native code, and observe events natively on iOS #906).The service now reads its settings with its own context, so both calls get their foreground service. The null check on the module covers both; without the module, the notification opens no activity.
Tests
tests/android/CallKeepForegroundServiceTest.kt, with a small options helper that the existing test now uses too. Run in the example app (React Native 0.87.1) on a Galaxy S24, Android 16:theForegroundServiceKeepsTheAppsOwnChannel(importance kept, name fromchannelName)expected:<4> but was:<2>theForegroundServiceStartsWithoutTheModule(stored settings, no module, incoming call added through Telecom)NullPointerExceptionongetCurrentReactActivity()makeOutgoingCallwakes the app up, which would start React Native inside the test process../gradlew :react-native-callkeep:lintDebugreports no errors, andnpm run checkpasses.endingTheSecondCallKeepsTheForegroundServiceOfTheFirstfails on this device ("second call not created"), on master as well as on this branch. I haven't looked into it here.RECORD_AUDIOgranted for these fixtures. Its manifest declaresphoneCall|microphone, and without the permission Android 14+ refuses the service:SecurityException: Starting FGS with type microphone ... requires permissions. That is the case feat(android): foregroundServiceType and endCallsOnHostDestroy options #905'sforegroundServiceTypeaddresses.🤖 Generated with Claude Code