Resolved telecom framework conflicts

Cleaned thread handling
More synchonizations
This commit is contained in:
Juha Heinanen
2026-05-25 10:40:41 +03:00
parent a54d79591e
commit 78aed2660c
4 changed files with 173 additions and 116 deletions

View File

@ -7,11 +7,6 @@
#include <baresip.h> #include <baresip.h>
#include "logger.h" #include "logger.h"
enum
{
ASYNC_WORKERS = 4
};
typedef struct baresip_context typedef struct baresip_context
{ {
JavaVM *javaVM; JavaVM *javaVM;
@ -22,6 +17,36 @@ typedef struct baresip_context
BaresipContext g_ctx; BaresipContext g_ctx;
enum
{
ASYNC_WORKERS = 4
};
static pthread_key_t g_thread_key;
static void detach_thread(void *env)
{
(void)env;
LOGD("detaching thread from JVM\n");
(*g_ctx.javaVM)->DetachCurrentThread(g_ctx.javaVM);
}
static JNIEnv *get_jni_env(void)
{
JNIEnv *env;
jint res = (*g_ctx.javaVM)->GetEnv(g_ctx.javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*g_ctx.javaVM)->AttachCurrentThread(g_ctx.javaVM, &env, NULL);
if (res == JNI_OK) {
pthread_setspecific(g_thread_key, env);
} else {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return NULL;
}
}
return env;
}
static int vprintf_null(const char *p, size_t size, void *arg) static int vprintf_null(const char *p, size_t size, void *arg)
{ {
(void)p; (void)p;
@ -257,23 +282,23 @@ static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
return; return;
} }
JavaVM *javaVM = g_ctx.javaVM; JNIEnv *env = get_jni_env();
JNIEnv *env; if (!env) return;
jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) { if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) return;
LOGD("failed to get javaVM environment, ErrorCode = %d\n", res);
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
jmethodID methodId = jmethodID methodId =
(*env)->GetMethodID(env, g_ctx.mainActivityClz, "uaEvent", "(Ljava/lang/String;JJ)V"); (*env)->GetMethodID(env, g_ctx.mainActivityClz, "uaEvent", "(Ljava/lang/String;JJ)V");
jstring jEvent = (*env)->NewStringUTF(env, event_buf); jstring jEvent = (*env)->NewStringUTF(env, event_buf);
LOGD("sending ua/call %ld/%ld event %s\n", (long)ua, (long)call, event_buf); LOGD("sending ua/call %ld/%ld event %s\n", (long)ua, (long)call, event_buf);
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, jEvent, (jlong)ua, (jlong)call); if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, jEvent, (jlong)ua, (jlong)call);
if ((*env)->ExceptionCheck(env)) {
LOGE("uaEvent method call failed\n");
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, jEvent); (*env)->DeleteLocalRef(env, jEvent);
} }
@ -291,16 +316,10 @@ static void message_handler(
return; return;
} }
JavaVM *javaVM = g_ctx.javaVM; JNIEnv *env = get_jni_env();
JNIEnv *env; if (!env) return;
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);
if (res != JNI_OK) { if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) return;
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageEvent", jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageEvent",
"(JLjava/lang/String;Ljava/lang/String;[B)V"); "(JLjava/lang/String;Ljava/lang/String;[B)V");
@ -319,7 +338,14 @@ static void message_handler(
(*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0); (*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);
LOGD("sending message %ld/%s/%s/%.*s\n", (long)ua, peer_buf, ctype_buf, (int)size, LOGD("sending message %ld/%s/%s/%.*s\n", (long)ua, peer_buf, ctype_buf, (int)size,
mbuf_buf(body)); mbuf_buf(body));
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, (jlong)ua, jPeer, jCtype, jMsg); if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, (jlong)ua, jPeer, jCtype, jMsg);
if ((*env)->ExceptionCheck(env)) {
LOGE("messageEvent method call failed\n");
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, jCtype); (*env)->DeleteLocalRef(env, jCtype);
(*env)->DeleteLocalRef(env, jPeer); (*env)->DeleteLocalRef(env, jPeer);
(*env)->DeleteLocalRef(env, jMsg); (*env)->DeleteLocalRef(env, jMsg);
@ -341,24 +367,30 @@ static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
LOGD("send_response_handler received response '%u %s' at %s\n", msg->scode, reason_buf, LOGD("send_response_handler received response '%u %s' at %s\n", msg->scode, reason_buf,
native_time); native_time);
JavaVM *javaVM = g_ctx.javaVM; JNIEnv *env = get_jni_env();
JNIEnv *env; if (!env) {
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6); mem_deref(native_time);
if (res != JNI_OK) { return;
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); }
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res); if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) {
mem_deref(native_time); mem_deref(native_time);
return; return;
}
} }
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageResponse", jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageResponse",
"(ILjava/lang/String;Ljava/lang/String;)V"); "(ILjava/lang/String;Ljava/lang/String;)V");
jstring javaReason = (*env)->NewStringUTF(env, reason_buf); jstring javaReason = (*env)->NewStringUTF(env, reason_buf);
jstring javaTime = (*env)->NewStringUTF(env, native_time); jstring javaTime = (*env)->NewStringUTF(env, native_time);
if (methodId) if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, msg->scode, javaReason, javaTime); (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, msg->scode, javaReason,
javaTime);
if ((*env)->ExceptionCheck(env)) {
LOGE("uaEvent messageResponse method call failed\n");
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, javaReason); (*env)->DeleteLocalRef(env, javaReason);
(*env)->DeleteLocalRef(env, javaTime); (*env)->DeleteLocalRef(env, javaTime);
@ -435,6 +467,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
g_ctx.mainActivityClz = NULL; g_ctx.mainActivityClz = NULL;
g_ctx.mainActivityObj = NULL; g_ctx.mainActivityObj = NULL;
if (pthread_key_create(&g_thread_key, detach_thread) != 0) {
LOGE("failed to create pthread key\n");
}
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }

View File

@ -51,6 +51,7 @@ import android.os.VibratorManager
import android.provider.ContactsContract import android.provider.ContactsContract
import android.provider.Settings import android.provider.Settings
import android.system.OsConstants import android.system.OsConstants
import android.telecom.Connection
import android.telecom.DisconnectCause import android.telecom.DisconnectCause
import android.telecom.PhoneAccountHandle import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager import android.telecom.TelecomManager
@ -1003,10 +1004,14 @@ class BaresipService: Service() {
stopMediaPlayer() stopMediaPlayer()
ConnectionService.connections[callp]?.setActive() ConnectionService.connections[callp]?.setActive()
ensureCommunicationMode() ensureCommunicationMode()
if (call!!.status.value == "incoming") Handler(Looper.getMainLooper()).post {
call.status.value = "answered" if (call != null) {
else if (call.status.value == "incoming")
return call.status.value = "answered"
else
return@post
}
}
} }
"call redirect" -> { "call redirect" -> {
stopMediaPlayer() stopMediaPlayer()
@ -1016,64 +1021,85 @@ class BaresipService: Service() {
stopMediaPlayer() stopMediaPlayer()
nm.cancel(CALL_NOTIFICATION_ID) nm.cancel(CALL_NOTIFICATION_ID)
Log.d(TAG, "AoR $aor call $callp established") Log.d(TAG, "AoR $aor call $callp established")
call!!.status.value = "connected" Handler(Looper.getMainLooper()).post {
call.onhold = false if (call != null) {
call.startTime = GregorianCalendar() call.status.value = "connected"
updateStatusNotification() call.onhold = false
call.startTime = GregorianCalendar()
if (call.conferenceCall)
Api.cmd_exec("conference")
}
updateStatusNotification()
proximitySensing(proximitySensing)
}
if (!isMainVisible) if (!isMainVisible)
return return
} }
"call update" -> { "call update" -> {
val newHeldState = when (ev[1].toInt()) {Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true val newHeldState = when (ev[1].toInt()) {
Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true
else -> false else -> false
} }
val connection = ConnectionService.connections[callp] val connection = ConnectionService.connections[callp]
if (call!!.held && !newHeldState) { Handler(Looper.getMainLooper()).post {
Log.d(TAG, "Call ${call.callp} un-held by peer.") if (call != null) {
call.onhold = false if (call.held && !newHeldState) {
// Use a Coroutine with a small delay to let the SIP Log.d(TAG, "Call ${call.callp} un-held by peer.")
// transaction (the re-INVITE from the peer) finish call.onhold = false
// before trying to hold the other call and resume this one. // Use a Coroutine with a small delay to let the SIP
CoroutineScope(Dispatchers.Main).launch { // transaction (the re-INVITE from the peer) finish
delay(100) // before trying to hold the other call and resume this one.
call.resume() CoroutineScope(Dispatchers.Main).launch {
delay(100)
call.resume()
}
}
call.held = newHeldState
if (newHeldState) {
// Peer put us on hold
call.showOnHoldNotice.value = true
call.callOnHold.value = true
if (connection?.state != Connection.STATE_HOLDING)
connection?.setOnHold()
} else {
// Peer un-held us
call.showOnHoldNotice.value = false
if (!call.onhold) {
call.callOnHold.value = false
if (connection?.state != Connection.STATE_ACTIVE)
connection?.setActive()
}
}
if (call.state() == Api.CALL_STATE_EARLY) {
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
stopMediaPlayer()
}
if (call.status.value == "connected" && !call.held && !call.onhold) {
if (call.callOnHold.value || call.showOnHoldNotice.value) {
Log.d(
TAG,
"Safety guard: Clearing stuck hold flags for ${call.callp}"
)
call.callOnHold.value = false
call.showOnHoldNotice.value = false
connection?.setActive()
}
}
} }
} }
call.held = newHeldState if (!isMainVisible || call?.status?.value != "connected")
if (newHeldState) {
// Peer put us on hold
call.showOnHoldNotice.value = true
call.callOnHold.value = true
connection?.setOnHold()
} else {
// Peer un-held us
call.showOnHoldNotice.value = false
if (!call.onhold) {
call.callOnHold.value = false
connection?.setActive()
}
}
if (call.state() == Api.CALL_STATE_EARLY) {
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
stopMediaPlayer()
}
if (call.status.value == "connected" && !call.held && !call.onhold) {
if (call.callOnHold.value || call.showOnHoldNotice.value) {
Log.d(TAG, "Safety guard: Clearing stuck hold flags for ${call.callp}")
call.callOnHold.value = false
call.showOnHoldNotice.value = false
connection?.setActive()
}
}
if (!isMainVisible || call.status.value != "connected")
return return
} }
"call verified", "call secure" -> { "call verified", "call secure" -> {
if (ev[0] == "call secure") { Handler(Looper.getMainLooper()).post {
call!!.security = R.color.colorTrafficYellow if (call != null) {
} else { if (ev[0] == "call secure") {
call!!.security = R.color.colorTrafficGreen call.security = R.color.colorTrafficYellow
call.zid = ev[1] } else {
call.security = R.color.colorTrafficGreen
call.zid = ev[1]
}
}
} }
if (!isMainVisible) if (!isMainVisible)
return return
@ -1147,7 +1173,6 @@ class BaresipService: Service() {
else -> DisconnectCause.REMOTE else -> DisconnectCause.REMOTE
} }
connection.setDisconnected(DisconnectCause(cause)) connection.setDisconnected(DisconnectCause(cause))
connection.destroy()
ConnectionService.connections.remove(callp) ConnectionService.connections.remove(callp)
} }
if (call != null) { if (call != null) {
@ -2661,13 +2686,15 @@ class BaresipService: Service() {
if (!servers.contains(server)) servers.add(server) if (!servers.contains(server)) servers.add(server)
} }
// Update if change // Update if change
if (servers != dnsServers) { synchronized(dnsServers) {
if (isServiceRunning && Config.updateDnsServers(servers) != 0) { if (servers != dnsServers) {
Log.w(TAG, "Failed to update DNS servers '${servers}'") if (isServiceRunning && Config.updateDnsServers(servers) != 0) {
} else { Log.w(TAG, "Failed to update DNS servers '${servers}'")
// Log.d(TAG, "Updated DNS servers: '${servers}'") } else {
dnsServers = servers // Log.d(TAG, "Updated DNS servers: '${servers}'")
return true dnsServers = servers
return true
}
} }
} }
return false return false

View File

@ -33,7 +33,6 @@ class ConnectionService : ConnectionService() {
fun onCallClosed(callp: Long) { fun onCallClosed(callp: Long) {
connections[callp]?.let { connections[callp]?.let {
it.setDisconnected(DisconnectCause(DisconnectCause.REMOTE)) it.setDisconnected(DisconnectCause(DisconnectCause.REMOTE))
it.destroy()
connections.remove(callp) connections.remove(callp)
} }
} }

View File

@ -2427,10 +2427,14 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
when (ev[0]) { when (ev[0]) {
"call rejected" -> { "call rejected" -> {
if (aor == viewModel.selectedAor.value)
viewModel.triggerAccountUpdate()
} }
"call incoming", "call outgoing" -> { "call outgoing" -> {
val callp = params[1] as Long
if (!BaresipService.isMainVisible)
viewModel.navigateToHome()
spinToAor(viewModel, aor, Call.ofCallp(callp))
}
"call incoming" -> {
val callp = params[1] as Long val callp = params[1] as Long
if (!BaresipService.isMainVisible) if (!BaresipService.isMainVisible)
viewModel.navigateToHome() viewModel.navigateToHome()
@ -2464,7 +2468,6 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
} }
showDialog.value = true showDialog.value = true
} }
viewModel.triggerAccountUpdate()
} }
"call established" -> { "call established" -> {
(ctx as? Activity)?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) (ctx as? Activity)?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@ -2472,16 +2475,11 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
viewModel.dialerState.callButtonsEnabled.value = true // Re-enable dialer viewModel.dialerState.callButtonsEnabled.value = true // Re-enable dialer
val callp = params[1] as Long val callp = params[1] as Long
val call = Call.ofCallp(callp) val call = Call.ofCallp(callp)
if (call != null) { if (call != null)
call.dtmfText.value = "" call.dtmfText.value = ""
if (call.conferenceCall)
Api.cmd_exec("conference")
}
viewModel.triggerAccountUpdate(call)
} }
} }
"call update" -> { "call update" -> {
viewModel.triggerAccountUpdate()
} }
"call verify" -> { "call verify" -> {
val callp = params[1] as Long val callp = params[1] as Long
@ -2524,7 +2522,6 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
} }
if (aor == viewModel.selectedAor.value) { if (aor == viewModel.selectedAor.value) {
call.securityIconTint.value = call.security call.securityIconTint.value = call.security
viewModel.triggerAccountUpdate(call)
} }
} }
"call transfer", "transfer show" -> { "call transfer", "transfer show" -> {
@ -2568,9 +2565,10 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
showCall(ctx, viewModel, ua) showCall(ctx, viewModel, ua)
} }
"call closed" -> { "call closed" -> {
if (Call.calls().isEmpty()) val calls = Call.calls()
if (calls.isEmpty())
(ctx as? Activity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) (ctx as? Activity)?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
viewModel.updateCalls(Call.calls().toList()) viewModel.updateCalls(calls.toList())
val activity = ctx as? Activity val activity = ctx as? Activity
if (activity != null) { if (activity != null) {
val kgm = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager val kgm = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
@ -2583,8 +2581,6 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
viewModel.dialerState.callButtonsEnabled.value = true viewModel.dialerState.callButtonsEnabled.value = true
ua.account.resumeUri = "" ua.account.resumeUri = ""
viewModel.triggerAccountUpdate() viewModel.triggerAccountUpdate()
if (acc.missedCalls)
viewModel.triggerAccountUpdate()
} }
} }
"message", "message show", "message reply" -> { "message", "message show", "message reply" -> {
@ -2602,13 +2598,12 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
break break
} }
} }
if (aor == viewModel.selectedAor.value)
viewModel.triggerAccountUpdate()
} }
else -> Log.e(TAG, "Unknown event '${ev[0]}'") else -> Log.e(TAG, "Unknown event '${ev[0]}'")
} }
viewModel.updateCalls(Call.calls().toList()) viewModel.updateCalls(Call.calls().toList())
viewModel.triggerAccountUpdate()
handleNextEvent() handleNextEvent()
} }