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 "logger.h"
enum
{
ASYNC_WORKERS = 4
};
typedef struct baresip_context
{
JavaVM *javaVM;
@ -22,6 +17,36 @@ typedef struct baresip_context
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)
{
(void)p;
@ -257,23 +282,23 @@ static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
return;
}
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
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;
}
}
JNIEnv *env = get_jni_env();
if (!env) return;
if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) return;
jmethodID methodId =
(*env)->GetMethodID(env, g_ctx.mainActivityClz, "uaEvent", "(Ljava/lang/String;JJ)V");
jstring jEvent = (*env)->NewStringUTF(env, 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);
}
@ -291,16 +316,10 @@ static void message_handler(
return;
}
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
JNIEnv *env = get_jni_env();
if (!env) return;
if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) return;
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageEvent",
"(JLjava/lang/String;Ljava/lang/String;[B)V");
@ -319,7 +338,14 @@ static void message_handler(
(*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);
LOGD("sending message %ld/%s/%s/%.*s\n", (long)ua, peer_buf, ctype_buf, (int)size,
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, jPeer);
(*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,
native_time);
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
mem_deref(native_time);
return;
}
JNIEnv *env = get_jni_env();
if (!env) {
mem_deref(native_time);
return;
}
if (!g_ctx.mainActivityClz || !g_ctx.mainActivityObj) {
mem_deref(native_time);
return;
}
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageResponse",
"(ILjava/lang/String;Ljava/lang/String;)V");
jstring javaReason = (*env)->NewStringUTF(env, reason_buf);
jstring javaTime = (*env)->NewStringUTF(env, native_time);
if (methodId)
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, msg->scode, javaReason, javaTime);
if (methodId) {
(*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, javaTime);
@ -435,6 +467,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
g_ctx.mainActivityClz = 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;
}

View File

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

View File

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

View File

@ -2427,10 +2427,14 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
when (ev[0]) {
"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
if (!BaresipService.isMainVisible)
viewModel.navigateToHome()
@ -2464,7 +2468,6 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
}
showDialog.value = true
}
viewModel.triggerAccountUpdate()
}
"call established" -> {
(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
val callp = params[1] as Long
val call = Call.ofCallp(callp)
if (call != null) {
if (call != null)
call.dtmfText.value = ""
if (call.conferenceCall)
Api.cmd_exec("conference")
}
viewModel.triggerAccountUpdate(call)
}
}
"call update" -> {
viewModel.triggerAccountUpdate()
}
"call verify" -> {
val callp = params[1] as Long
@ -2524,7 +2522,6 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
}
if (aor == viewModel.selectedAor.value) {
call.securityIconTint.value = call.security
viewModel.triggerAccountUpdate(call)
}
}
"call transfer", "transfer show" -> {
@ -2568,9 +2565,10 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
showCall(ctx, viewModel, ua)
}
"call closed" -> {
if (Call.calls().isEmpty())
val calls = Call.calls()
if (calls.isEmpty())
(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
if (activity != null) {
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
ua.account.resumeUri = ""
viewModel.triggerAccountUpdate()
if (acc.missedCalls)
viewModel.triggerAccountUpdate()
}
}
"message", "message show", "message reply" -> {
@ -2602,13 +2598,12 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
break
}
}
if (aor == viewModel.selectedAor.value)
viewModel.triggerAccountUpdate()
}
else -> Log.e(TAG, "Unknown event '${ev[0]}'")
}
viewModel.updateCalls(Call.calls().toList())
viewModel.triggerAccountUpdate()
handleNextEvent()
}