diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c index fc9917c1..5bd72bce 100644 --- a/app/src/main/cpp/baresip.c +++ b/app/src/main/cpp/baresip.c @@ -7,11 +7,6 @@ #include #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; } diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index b00f9f60..8a83e388 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -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 diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt b/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt index 04c43ea8..9017352a 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt @@ -33,7 +33,6 @@ class ConnectionService : ConnectionService() { fun onCallClosed(callp: Long) { connections[callp]?.let { it.setDisconnected(DisconnectCause(DisconnectCause.REMOTE)) - it.destroy() connections.remove(callp) } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt index f814dcad..ad3df21b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt @@ -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() }