From b1ff3ecc0ea737437bd28b6b3000011de3f949c1 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Sun, 23 Aug 2020 13:26:28 +0300 Subject: [PATCH] Added call transfer capability. --- app/src/main/cpp/baresip.c | 17 +++++- .../com/tutpro/baresip/BaresipService.kt | 4 +- .../main/kotlin/com/tutpro/baresip/Call.kt | 5 ++ .../kotlin/com/tutpro/baresip/MainActivity.kt | 59 ++++++++++++++++++- app/src/main/res/drawable/call_transfer.xml | 5 ++ app/src/main/res/layout/activity_main.xml | 20 ++++++- app/src/main/res/values-fi/strings.xml | 14 +++-- app/src/main/res/values/strings.xml | 14 +++-- 8 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 app/src/main/res/drawable/call_transfer.xml diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c index aa22290c..559bd6e2 100644 --- a/app/src/main/cpp/baresip.c +++ b/app/src/main/cpp/baresip.c @@ -1285,7 +1285,6 @@ Java_com_tutpro_baresip_Api_ua_1debug(JNIEnv *env, jobject thiz, jstring javaUA) ua_debug_log(ua); } - JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Call_call_1connect(JNIEnv *env, jobject thiz, jstring javaCall, jstring javaPeer) { @@ -1375,6 +1374,22 @@ Java_com_tutpro_baresip_Call_call_1unhold(JNIEnv *env, jobject thiz, jstring jav return ret; } +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_Call_call_1transfer(JNIEnv *env, jobject thiz, jstring jCall, + jstring jPeer) { + const char *native_call = (*env)->GetStringUTFChars(env, jCall, 0); + const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0); + struct call *call = (struct call *)strtoul(native_call, NULL, 10); + LOGD("transfering call %s to %s\n", native_call, native_peer); + re_thread_enter(); + int err = call_transfer(call, native_peer); + re_thread_leave(); + if (err) LOGW("call_transfer error: %d\n", err); + (*env)->ReleaseStringUTFChars(env, jCall, native_call); + (*env)->ReleaseStringUTFChars(env, jPeer, native_peer); + return err; +} + JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1peeruri(JNIEnv *env, jobject thiz, jstring javaCall) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index cd756c46..b4d58c73 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -695,7 +695,7 @@ class BaresipService: Service() { .setContentIntent(pi) .setDefaults(Notification.DEFAULT_SOUND) .setAutoCancel(true) - .setContentTitle(getString(R.string.transfer_request)) + .setContentTitle(getString(R.string.transfer_request_to)) .setContentText(target) if (Build.VERSION.SDK_INT < 26) { nb.setVibrate(LongArray(0)) @@ -757,7 +757,7 @@ class BaresipService: Service() { "transfer failed" -> { Log.d(LOG_TAG, "AoR $aor hanging up call $callp with ${ev[1]}") Api.ua_hangup(uap, callp, 0, "") - return + if (!Utils.isVisible()) return } } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt index 8ab069a8..290649ab 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt @@ -39,6 +39,10 @@ class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: S return call_unhold(callp) } + fun transfer(uri: String): Int { + return call_transfer(callp, uri) + } + fun sendDigit(digit: Char): Int { return call_send_digit(callp, digit) } @@ -66,6 +70,7 @@ class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: S private external fun call_connect(callp: String, peer_uri: String): Int private external fun call_hold(callp: String): Int private external fun call_unhold(callp: String): Int + private external fun call_transfer(callp: String, peer_uri: String): Int private external fun call_send_digit(callp: String, digit: Char): Int private external fun call_notify_sipfrag(callp: String, code: Int, reason: String) private external fun call_start_audio(callp: String) diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index e5c66b95..d0df93ff 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -36,6 +36,7 @@ class MainActivity : AppCompatActivity() { internal lateinit var answerButton: ImageButton internal lateinit var rejectButton: ImageButton internal lateinit var holdButton: ImageButton + internal lateinit var transferButton: ImageButton internal lateinit var voicemailButton: ImageButton internal lateinit var contactsButton: ImageButton internal lateinit var messagesButton: ImageButton @@ -82,6 +83,7 @@ class MainActivity : AppCompatActivity() { answerButton = findViewById(R.id.answerButton) as ImageButton rejectButton = findViewById(R.id.rejectButton) as ImageButton holdButton = findViewById(R.id.holdButton) as ImageButton + transferButton = findViewById(R.id.transferButton) as ImageButton dtmf = findViewById(R.id.dtmf) as EditText infoButton = findViewById(R.id.info) as ImageButton voicemailButton = findViewById(R.id.voicemailButton) as ImageButton @@ -329,6 +331,11 @@ class MainActivity : AppCompatActivity() { } } + transferButton.setOnClickListener { + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] + callTransfer(Call.uaCalls(ua, "")[0]) + } + infoButton.setOnClickListener { val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val calls = Call.uaCalls(ua, "") @@ -748,12 +755,12 @@ class MainActivity : AppCompatActivity() { return } val titleView = View.inflate(this, R.layout.alert_title, null) as TextView - titleView.text = getString(R.string.transfer) + titleView.text = getString(R.string.transfer_request) val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]), Utils.aorDomain(aor)) with (AlertDialog.Builder(this)) { setCustomTitle(titleView) - setMessage(String.format(getString(R.string.transfer_query), + setMessage(String.format(getString(R.string.transfer_request_query), target)) setPositiveButton(getString(R.string.yes)) { dialog, _ -> if (call in Call.calls()) @@ -782,6 +789,11 @@ class MainActivity : AppCompatActivity() { call(ua, ev[1], "transferring") showCall(ua) } + "transfer failed" -> { + Toast.makeText(applicationContext, + "${getString(R.string.transfer_failed)}: ${ev[1].trim()}", + Toast.LENGTH_LONG).show() + } "call closed" -> { if (aor == aorSpinner.tag) { callUri.inputType = InputType.TYPE_CLASS_TEXT + @@ -1031,6 +1043,45 @@ class MainActivity : AppCompatActivity() { } } + private fun callTransfer(call: Call) { + val layout = LayoutInflater.from(this) + .inflate(R.layout.call_transfer_dialog, findViewById(android.R.id.content) as ViewGroup, + false) + val titleView = layout.findViewById(R.id.title) as TextView + titleView.text = getString(R.string.call_transfer) + val transferUri = layout.findViewById(R.id.transferUri) as AutoCompleteTextView + transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, + Contact.contacts().map { Contact -> Contact.name })) + transferUri.threshold = 2 + with (AlertDialog.Builder(this)) { + setView(layout) + setPositiveButton(R.string.transfer) { dialog, _ -> + dialog.dismiss() + val uriText = transferUri.text.toString().trim() + if (uriText.length > 0) { + var uri = ContactsActivity.findContactURI(uriText) + if (!uri.startsWith("sip:")) { + uri = "sip:$uri" + if (!uri.contains("@")) { + val aor = call.ua.account.aor + val host = aor.substring(aor.indexOf("@") + 1) + uri = "$uri@$host" + } + } + if (!Utils.checkSipUri(uri)) + Utils.alertView(applicationContext, getString(R.string.notice), + String.format(getString(R.string.invalid_sip_uri), uri)) + else + call.transfer(uri) + } + } + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + } + show() + } + } + private fun askPassword(title: String, ua: UserAgent? = null) { val layout = LayoutInflater.from(this) .inflate(R.layout.password_dialog, findViewById(android.R.id.content) as ViewGroup, @@ -1303,6 +1354,7 @@ class MainActivity : AppCompatActivity() { answerButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE holdButton.visibility = View.INVISIBLE + transferButton.visibility = View.INVISIBLE dtmf.visibility = View.INVISIBLE dialpadButton.isEnabled = true infoButton.visibility = View.INVISIBLE @@ -1334,6 +1386,7 @@ class MainActivity : AppCompatActivity() { answerButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE holdButton.visibility = View.INVISIBLE + transferButton.visibility = View.INVISIBLE dtmf.visibility = View.INVISIBLE dialpadButton.isEnabled = false infoButton.visibility = View.INVISIBLE @@ -1347,6 +1400,7 @@ class MainActivity : AppCompatActivity() { rejectButton.visibility = View.VISIBLE rejectButton.isEnabled = true holdButton.visibility = View.INVISIBLE + transferButton.visibility = View.INVISIBLE dtmf.visibility = View.INVISIBLE dialpadButton.isEnabled = false infoButton.visibility = View.INVISIBLE @@ -1370,6 +1424,7 @@ class MainActivity : AppCompatActivity() { holdButton.setImageResource(R.drawable.pause) } holdButton.visibility = View.VISIBLE + transferButton.visibility = View.VISIBLE dtmf.visibility = View.VISIBLE dtmf.isEnabled = true dtmf.requestFocus() diff --git a/app/src/main/res/drawable/call_transfer.xml b/app/src/main/res/drawable/call_transfer.xml new file mode 100644 index 00000000..54d8bb19 --- /dev/null +++ b/app/src/main/res/drawable/call_transfer.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index aaaa8ad7..51f4e707 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -140,12 +140,26 @@ android:contentDescription="@string/hold" > - + + + Vastaa Hylkää Puhelu soittajalta - Puhelun siirtopyyntö kohteeseen + Puhelun siirtopyyntö kohteeseen Viesti lähettäjältä Kosketa käynnistääksesi sovelluksen @@ -319,6 +319,10 @@ Videopyyntö Salli video puhelussa \'%1$s\'? Aseta puhelu pitoon + Puhelun siirto + Siirron kohde + Siirto + Siirto epäonnistui DTMF Puhelutiedot Ei saatavilla @@ -346,10 +350,10 @@ Todennuspyyntö Todennatko SAS:n <%1$s> <%2$s>? - Siirtopyyntö - Hyväksytkö tämän puhelun siirron kohteeseen \'%1$s\'? - Puhelu epäonnistui. - Puhelu on päättynyt. + Siirtopyyntö + Hyväksytkö tämän puhelun siirron kohteeseen \'%1$s\'? + Puhelu epäonnistui + Puhelu on päättynyt Tämä puhelu EI ole turvallinen! Tämä puhelu on turvallinen, mutta kohdetta ei ole todennettu! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e92a9d7f..bd578549 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,7 +132,7 @@ Answer Reject Call from - Call transfer request to + Call transfer request to Message from Tap to start application @@ -284,6 +284,10 @@ Video Request Accept video call with \'%1$s\'? Hold + Call Transfer + Transfer destination + Transfer + Transfer Failed DTMF Call Info No info available @@ -309,10 +313,10 @@ Registering of \`%1$s\` failed. Verify Request Do you want to verify SAS <%1$s> <%2$s>? - Transfer Request - Do you accept to transfer this call to \'%1$s\'? - Call failed. - Call is closed. + Transfer Request + Do you accept to transfer this call to \'%1$s\'? + Call failed + Call is closed This call is NOT secure! This call is SECURE, but peer is NOT verified! This call is SECURE and peer is VERIFIED!