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!