I am trying to add outbound SIP calling into an Android app using android.net.sip.SipManager
, I get a call to onRingBack
but when the other side answers the call I get a call to onError
with code -4 and message android.system.ErrnoException: sendto failed: ENETUNREACH (Network is unreachable)
.
I suspect that this is some kind of firewall or network issue because depending on the network I use, I get the ENETUNREACH error at different points in the process. With my VPN enabled, I get it before the onRingBack
callback. With a public WiFi network, I never get the onError
callback at all, I just get no callbacks after onRingBack
. And with my personal WiFi network, I get the onError
as described above. Yet somehow the LinPhoneAndroid app does not have this problem, so there must be a way to code around this.
For my test setup this, I created two accounts at linphone.org:
I use the first account with the LinPhone iOS app on my iPhone 11. I use the second account with the LinPhone Android app on my Google Pixel 4a with Android 12. I verified I can successfully make calls from Android to iOS using these LinPhone apps.
I then coded my own app on Android to call the iPhone. It successfully registers with the SIP server with:
sipProfile = SipProfile.Builder(username, domain).setPassword(password).build()
val intent = Intent()
intent.action = "android.SipDemo.INCOMING_CALL"
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, Intent.FILL_IN_DATA)
sipManager.open(sipProfile, pendingIntent, null)
sipManager.setRegistrationListener(sipProfile.uriString, object: SipRegistrationListener {
override fun onRegistering(localProfileUri: String?) {
Log.d(TAG, "Registering")
}
override fun onRegistrationDone(localProfileUri: String?, expiryTime: Long) {
registrationExpiration = Date(expiryTime)
...
Log.d(TAG, "Registration done. LocalProfileUri: $localProfileUri Expiry Time: ${registrationExpiration}")
}
override fun onRegistrationFailed(
localProfileUri: String?,
errorCode: Int,
errorMessage: String?
) {
Log.d(TAG, "Registration failed. LocalProfileUri: $localProfileUri, Error code: $errorCode, Error MessagE: $errorMessage")
}
})
I can then initiate an outbound call to the iPhone with sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30)
, and the iPhone actually rings and can answer the call which launches the LinPhone iOS app and shows a call in progress timer. **When the iPhone starts ringing, my Android code gets a callback to onRingingBack
. But when the iPhone answers the call it gets a call to onError
with code -4 an d messagesendto failed: ENETUNREACH (Network is unreachable)
Here is the full code that initiates the call and implements the callbacks:
audioCallListener = object: SipAudioCall.Listener() {
public override fun onError(
call: SipAudioCall?,
errorCode: Int,
errorMessage: String?
) {
super.onError(call, errorCode, errorMessage)
Log.d(TAG, "Error making call code: $errorCode, message: $errorMessage")
}
public override fun onReadyToCall(call: SipAudioCall?) {
Log.d(TAG, "ready to call")
super.onReadyToCall(call)
}
public override fun onRingingBack(call: SipAudioCall?) {
Log.d(TAG, "onRingingBack")
super.onRingingBack(call)
}
public override fun onCalling(call: SipAudioCall?) {
Log.d(TAG, "onCalling")
super.onCalling(call)
}
public override fun onCallHeld(call: SipAudioCall?) {
Log.d(TAG, "onCallHeld")
super.onCallHeld(call)
}
public override fun onCallBusy(call: SipAudioCall?) {
Log.d(TAG, "onCallBusy")
super.onCallBusy(call)
}
public override fun onChanged(call: SipAudioCall?) {
val state = call?.state ?: -1
Log.d(TAG, "onChanged state: ${state}")
super.onChanged(call)
}
public override fun onRinging(call: SipAudioCall?, caller: SipProfile?) {
Log.d(TAG, "Ringing...")
super.onRinging(call, caller)
}
public override fun onCallEstablished(call: SipAudioCall) {
mediaPlayer?.stop()
call.startAudio()
call.setSpeakerMode(true)
call.toggleMute()
Log.d(TAG, "Calls started")
super.onCallEstablished(call)
}
public override fun onCallEnded(call: SipAudioCall) {
Log.d(TAG, "Call ended")
super.onCallEnded(call)
}
}
GlobalScope.launch {
Log.d(TAG, "Making call from ${sipProfile.getUriString()} to ${sipAddress}")
try {
lastCall = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, audioCallListener, 30);
}
catch (e: SipException) {
Log.e(TAG, "Call failed", e)
}
}
Unfortunately, the answer is to give up on using these APIs. Google has:
This interface was deprecated in API level 31. SipManager and associated classes are no longer supported and should not be used as the basis of future VOIP apps.
See here