diff --git a/app/build.gradle b/app/build.gradle index b2f720ca..7f12ccaf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId = 'com.tutpro.baresip' minSdkVersion 21 targetSdkVersion 27 - versionCode = 36 - versionName = '4.1.2' + versionCode = 37 + versionName = '5.0.0' externalNativeBuild { cmake { cFlags '-DHAVE_INTTYPES_H' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6c5cc781..f513fdb5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ - ReleaseStringUTFChars(env, javaMencid, mencid); return res; } + +JNIEXPORT jstring JNICALL +Java_com_tutpro_baresip_AccountKt_account_1medianat(JNIEnv *env, jobject thiz, jstring javaAcc) +{ + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *) strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + if (acc) { + const char *medianat = account_medianat(acc); + if (medianat) return (*env)->NewStringUTF(env, medianat); + } + return (*env)->NewStringUTF(env, ""); +} + +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_AccountKt_account_1set_1medianat(JNIEnv *env, jobject thiz, + jstring javaAcc, jstring javaMedNat) { + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *)strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + const char *mednat = (*env)->GetStringUTFChars(env, javaMedNat, 0); + int res; + if (strlen(mednat) > 0) + res = account_set_medianat(acc, mednat); + else + res = account_set_medianat(acc, NULL); + (*env)->ReleaseStringUTFChars(env, javaMedNat, mednat); + return res; +} + JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_AccountKt_account_1sipnat(JNIEnv *env, jobject thiz, jstring javaAcc) { @@ -715,6 +745,56 @@ Java_com_tutpro_baresip_AccountKt_account_1set_1sipnat(JNIEnv *env, jobject thiz return res; } +JNIEXPORT jstring JNICALL +Java_com_tutpro_baresip_AccountKt_account_1stun_1host(JNIEnv *env, jobject thiz, jstring javaAcc) +{ + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *) strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + if (acc) { + const char *stun_host = account_stun_host(acc); + if (stun_host) return (*env)->NewStringUTF(env, stun_host); + } + return (*env)->NewStringUTF(env, ""); +} + +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_AccountKt_account_1set_1stun_1host(JNIEnv *env, jobject thiz, + jstring javaAcc, jstring javaStunHost) { + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *)strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + const char *stun_host = (*env)->GetStringUTFChars(env, javaStunHost, 0); + int res; + if (strlen(stun_host) > 0) + res = account_set_stun_host(acc, stun_host); + else + res = account_set_stun_host(acc, NULL); + (*env)->ReleaseStringUTFChars(env, javaStunHost, stun_host); + return res; +} + +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_AccountKt_account_1stun_1port(JNIEnv *env, jobject thiz, jstring javaAcc) +{ + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *) strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + return account_stun_port(acc); +} + +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_AccountKt_account_1set_1stun_1port(JNIEnv *env, jobject thiz, + jstring javaAcc, jint javaStunPort) { + const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0); + struct account *acc = (struct account *)strtoul(native_acc, NULL, 10); + (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); + const uint16_t native_port = javaStunPort; + int res; + res = account_set_stun_port(acc, native_port); + return res; +} + JNIEXPORT jint JNICALL Java_com_tutpro_baresip_AccountKt_account_1set_1mwi(JNIEnv *env, jobject thiz, jstring javaAcc, jstring javaValue) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Account.kt b/app/src/main/kotlin/com/tutpro/baresip/Account.kt index 966bdb54..2174ec71 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Account.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Account.kt @@ -7,15 +7,25 @@ class Account(val accp: String) { var authUser = account_auth_user(accp) var authPass = account_auth_pass(accp) var outbound = ArrayList() + var mediaNat = account_medianat(accp) + var stunServer = "" var audioCodec = ArrayList() var regint = account_regint(accp) - var mediaenc = account_mediaenc(accp) + var mediaEnc = account_mediaenc(accp) var vmUri = account_vm_uri(accp) var vmNew = 0 var vmOld = 0 var missedCalls = false init { + val stunHost = account_stun_host(accp) + if (stunHost != "") { + val stunPort = account_stun_port(accp) + if (stunPort == 0) + stunServer = stunHost + else + stunServer = "$stunHost:$stunPort" + } var i = 0 while (true) { val ob = account_outbound(accp, i) @@ -59,6 +69,10 @@ class Account(val accp: String) { res = res + ";sipnat=outbound" } + if (mediaNat != "") res = res + ";medianat=${mediaNat}" + + if (stunServer != "") res = res + ";stunserver=\"stun:@${stunServer}\"" + if (audioCodec.size > 0) { var first = true res = res + ";audio_codecs=" @@ -71,7 +85,7 @@ class Account(val accp: String) { } } - if (mediaenc != "") res = res + ";mediaenc=${mediaenc}" + if (mediaEnc != "") res = res + ";mediaenc=${mediaEnc}" if (vmUri == "") res = res + ";mwi=no" @@ -159,8 +173,14 @@ external fun account_set_sipnat(acc: String, sipnat: String): Int external fun account_audio_codec(acc: String, ix: Int): String external fun account_regint(acc: String): Int external fun account_set_regint(acc: String, regint: Int): Int +external fun account_stun_host(acc: String): String +external fun account_stun_port(acc: String): Int +external fun account_set_stun_host(acc: String, host: String): Int +external fun account_set_stun_port(acc: String, port: Int): Int external fun account_mediaenc(acc: String): String -external fun account_set_mediaenc(acc: String, mencid: String): Int +external fun account_set_mediaenc(acc: String, mediaenc: String): Int +external fun account_medianat(acc: String): String +external fun account_set_medianat(acc: String, medianat: String): Int external fun account_set_audio_codecs(acc: String, codecs: String): Int external fun account_set_mwi(acc: String, value: String): Int external fun account_vm_uri(acc: String): String diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt index 4ae06e25..2a8238fa 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt @@ -1,6 +1,5 @@ package com.tutpro.baresip -import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.util.Log @@ -21,6 +20,8 @@ class AccountActivity : AppCompatActivity() { internal lateinit var authPass: EditText internal lateinit var outbound1: EditText internal lateinit var outbound2: EditText + internal lateinit var iceCheck: CheckBox + internal lateinit var stunServer: EditText internal lateinit var regCheck: CheckBox internal lateinit var mediaEnc: String internal lateinit var vmUri: EditText @@ -56,6 +57,16 @@ class AccountActivity : AppCompatActivity() { outbound2.setText(acc.outbound[1]) } + iceCheck = findViewById(R.id.Ice) as CheckBox + iceCheck.isChecked = acc.mediaNat == "ice" + iceCheck.setOnClickListener { + stunServer.isEnabled = iceCheck.isChecked + } + + stunServer = findViewById(R.id.StunServer) as EditText + stunServer.setText(acc.stunServer) + stunServer.isEnabled = iceCheck.isChecked + regCheck = findViewById(R.id.Register) as CheckBox regCheck.isChecked = acc.regint > 0 @@ -95,15 +106,15 @@ class AccountActivity : AppCompatActivity() { } } - mediaEnc = acc.mediaenc + mediaEnc = acc.mediaEnc val mediaEncSpinner = findViewById(R.id.mediaEncSpinner) as Spinner val mediaEncKeys = arrayListOf("zrtp", "dtls_srtpf", "srtp-mand", "srtp", "") val mediaEncVals = arrayListOf("ZRTP", "DTLS-SRTPF", "SRTP-MAND", "SRTP", "") - val keyIx = mediaEncKeys.indexOf(acc.mediaenc) + val keyIx = mediaEncKeys.indexOf(acc.mediaEnc) val keyVal = mediaEncVals.elementAt(keyIx) mediaEncKeys.removeAt(keyIx) mediaEncVals.removeAt(keyIx) - mediaEncKeys.add(0, acc.mediaenc) + mediaEncKeys.add(0, acc.mediaEnc) mediaEncVals.add(0, keyVal) val mediaEncAdapter = ArrayAdapter(this,android.R.layout.simple_spinner_item, mediaEncVals) @@ -131,8 +142,6 @@ class AccountActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { - val intent = Intent(this, MainActivity::class.java) - if (item.itemId == R.id.checkIcon) { val dn = displayName.text.toString().trim() @@ -170,7 +179,7 @@ class AccountActivity : AppCompatActivity() { val ap = authPass.text.toString().trim() if (ap != acc.authPass) { - if (Utils.checkPrintASCII(ap)) { + if (Utils.checkPrintAscii(ap)) { if (account_set_auth_pass(acc.accp, ap) == 0) { acc.authPass = account_auth_pass(acc.accp) // Log.d("Baresip", "New auth password is ${acc.authPass}") @@ -234,6 +243,46 @@ class AccountActivity : AppCompatActivity() { Log.e("Baresip", "Setting of regint failed") } + var newMediaNat = "" + if (iceCheck.isChecked) newMediaNat = "ice" + if (acc.mediaNat != newMediaNat) + if (account_set_medianat(acc.accp, newMediaNat) == 0) { + acc.mediaNat = account_medianat(acc.accp) + Log.d("Baresip", "New medianat is ${acc.mediaNat}") + save = true + } else { + Log.e("Baresip", "Setting of medianat failed") + } + + if (iceCheck.isChecked) { + val newStunServer = stunServer.text.toString().trim() + if (acc.stunServer != newStunServer) { + if ((newStunServer != "") && + !Utils.checkHostPort(newStunServer, false)) { + Utils.alertView(this, "Notice", + "Invalid STUN Server: $newStunServer") + return false + } + var host = "" + var port = "" + if (newStunServer != "") { + val hostPort = newStunServer.split(":") + host = hostPort[0] + if (hostPort.size == 2) port = hostPort[1] + } + if ((account_set_stun_host(acc.accp, host) == 0) && + (account_set_stun_port(acc.accp, port.toInt()) == 0)) { + acc.stunServer = account_stun_host(acc.accp) + if (port != "") + acc.stunServer += ":" + account_stun_port(acc.accp).toString() + Log.d("Baresip", "New StunServer is ${acc.stunServer}") + save = true + } else { + Log.e("Baresip", "Setting of StunServer failed") + } + } + } + val ac = ArrayList(LinkedHashSet(newCodecs.filter { it != "" } as ArrayList)) if (ac != acc.audioCodec) { Log.d("Baresip", "New codecs ${newCodecs.filter { it != "" }}") @@ -256,10 +305,10 @@ class AccountActivity : AppCompatActivity() { } } - if (mediaEnc != acc.mediaenc) { + if (mediaEnc != acc.mediaEnc) { if (account_set_mediaenc(acc.accp, mediaEnc) == 0) { - acc.mediaenc = account_mediaenc(acc.accp) - Log.d("Baresip", "New mediaenc is ${acc.mediaenc}") + acc.mediaEnc = account_mediaenc(acc.accp) + Log.d("Baresip", "New mediaenc is ${acc.mediaEnc}") save = true } else { Log.e("Baresip", "Setting of mediaenc $mediaEnc failed") @@ -324,6 +373,12 @@ class AccountActivity : AppCompatActivity() { findViewById(R.id.RegTitle) as TextView -> { Utils.alertView(this, "Register", getString(R.string.register)) } + findViewById(R.id.IceTitle) as TextView -> { + Utils.alertView(this, "Use ICE", getString(R.string.ice)) + } + findViewById(R.id.StunServerTitle) as TextView -> { + Utils.alertView(this, "STUN Server", getString(R.string.stunServer)) + } findViewById(R.id.AudioCodecsTitle) as TextView -> { Utils.alertView(this, "Audio Codecs", getString(R.string.auCodecs)) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt index 52f86a7b..2eb44f3f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt @@ -40,7 +40,7 @@ class AccountsActivity : AppCompatActivity() { } else if (Account.exists(aor)) { Log.e("Baresip", "Account $aor already exists") } else { - val ua = UserAgent.uaAlloc("<$aor>;regq=0.5;pubint=0;regint=0") + val ua = UserAgent.uaAlloc("<$aor>;stunserver=\"stun:stun.l.google.com:19302\";regq=0.5;pubint=0;regint=0") if (ua == null) { Log.e("Baresip", "Failed to allocate UA for $aor") Utils.alertView(this, "Notice", @@ -101,7 +101,7 @@ class AccountsActivity : AppCompatActivity() { var accounts = "" for (a in Account.accounts()) accounts = accounts + a.print() + "\n" Utils.putFileContents(File(filesPath + "/accounts"), accounts) - // Log.d("Baresip", "Saved accounts '${accounts}' to '${filesPath}/accounts") + // Log.d("Baresip", "Saved accounts '${accounts}' to '${filesPath}/accounts'") } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt new file mode 100644 index 00000000..37f9c8a9 --- /dev/null +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -0,0 +1,175 @@ +package com.tutpro.baresip + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.* + +import java.io.File + +class ConfigActivity : AppCompatActivity() { + + internal lateinit var configFile: File + internal lateinit var autoStart: CheckBox + internal lateinit var dnsServers: EditText + internal lateinit var opusBitRate: EditText + internal lateinit var iceLite: CheckBox + + private var oldAutoStart = "" + private var oldDnsServers = "" + private var oldOpusBitrate = "" + private var oldIceMode = "" + private var save = false + private var config = "" + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_config) + + configFile = File(applicationContext.filesDir.absolutePath + "/config") + config = Utils.getFileContents(configFile) + if (config.length <= 100) { + Utils.alertView(this, "Internal Error", "Failed to read config file") + finish() + return + } + + autoStart = findViewById(R.id.AutoStart) as CheckBox + val asCv = Utils.getNameValue(config, "auto_start") + oldAutoStart = if (asCv.size == 0) "no" else asCv[0] + autoStart.isChecked = oldAutoStart == "yes" + + dnsServers = findViewById(R.id.DnsServers) as EditText + val dsCv = Utils.getNameValue(config, "dns_server") + var dsTv = "" + for (ds in dsCv) dsTv += ", $ds" + oldDnsServers = dsTv.trimStart(',').trimStart(' ') + dnsServers.setText(oldDnsServers) + + opusBitRate = findViewById(R.id.OpusBitRate) as EditText + val obCv = Utils.getNameValue(config, "opus_bitrate") + oldOpusBitrate = if (obCv.size == 0) "28000" else obCv[0] + opusBitRate.setText(oldOpusBitrate) + + iceLite = findViewById(R.id.IceLite) as CheckBox + val imCv = Utils.getNameValue(config, "ice_mode") + oldIceMode = if (imCv.size == 0) "full" else imCv[0] + iceLite.isChecked = oldIceMode == "lite" + + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + val inflater = menuInflater + inflater.inflate(R.menu.check_icon, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + + val intent = Intent(this, MainActivity::class.java) + + if (item.itemId == R.id.checkIcon) { + + var autoStartString = "no" + if (autoStart.isChecked) autoStartString = "yes" + if (oldAutoStart != autoStartString) { + config = Utils.removeLinesStartingWithName(config, "auto_start") + config += "\nauto_start $autoStartString\n" + save = true + } + + val dnsServers = dnsServers.text.toString().trim() + if (dnsServers != oldDnsServers) { + if (!checkDnsServers(dnsServers)) { + Utils.alertView(this, "Notice", "Invalid DNS Servers: $dnsServers") + return false + } + config = Utils.removeLinesStartingWithName(config, "dns_server") + for (server in dnsServers.split(",")) + config += "\ndns_server ${server.trim()}\n" + save = true + } + + val opusBitRate = opusBitRate.text.toString().trim() + if (opusBitRate != oldOpusBitrate) { + if (!checkOpusBitRate(opusBitRate)) { + Utils.alertView(this, "Notice", "Invalid Opus Bit Rate: $opusBitRate") + return false + } + config = Utils.removeLinesStartingWithName(config, "opus_bitrate") + config += "\nopus_bitrate $opusBitRate\n" + save = true + } + + var iceModeString = "full" + if (iceLite.isChecked) iceModeString = "lite" + if (oldIceMode != iceModeString) { + config = Utils.removeLinesStartingWithName(config, "ice_mode") + config += "\nice_mode $iceModeString\n" + save = true + } + + if (save) { + var newConfig = "" + for (line in config.split("\n")) { + val trimmedLine = line.trim() + if (trimmedLine.startsWith("#") || (trimmedLine.length == 0)) continue + // Log.d("Baresip", "Config line $trimmedLine") + newConfig += trimmedLine.split("#")[0] + "\n" + } + Utils.putFileContents(configFile, newConfig) + // Api.reload_config() + } + setResult(RESULT_OK, intent) + finish() + return true + + } else if (item.itemId == android.R.id.home) { + + Log.d("Baresip", "Back array was pressed at Config") + setResult(Activity.RESULT_CANCELED, intent) + finish() + return true + + } else return super.onOptionsItemSelected(item) + } + + fun onClick(v: View) { + when (v) { + findViewById(R.id.AutoStartTitle) as TextView-> { + Utils.alertView(this, "Start Automatically", getString(R.string.autoStart)) + } + findViewById(R.id.DnsServersTitle) as TextView -> { + Utils.alertView(this, "DNS Servers", getString(R.string.dnsServers)) + } + findViewById(R.id.OpusBitRateTitle) as TextView-> { + Utils.alertView(this, "Opus Bit Rate", getString(R.string.opusBitRate)) + } + findViewById(R.id.IceLiteTitle) as TextView-> { + Utils.alertView(this, "ICE Lite Mode", getString(R.string.iceLite)) + } + } + } + + private fun checkDnsServers(dnsServers: String): Boolean { + if (dnsServers.length == 0) return true + for (server in dnsServers.split(",")) + if (!Utils.checkHostPort(server.trim(), true)) return false + return true + } + + private fun checkOpusBitRate(opusBitRate: String): Boolean { + val number = opusBitRate.toIntOrNull() + if (number == null) return false + return (number >=6000) && (number <= 510000) + } + +} + diff --git a/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt deleted file mode 100644 index 4ca6b3ca..00000000 --- a/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.tutpro.baresip - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.util.Log -import android.util.TypedValue -import android.view.Menu -import android.view.MenuItem -import android.widget.EditText -import android.widget.TextView - -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.OutputStreamWriter - -class EditConfigActivity : AppCompatActivity() { - - private var editText: EditText? = null - private var path: String? = null - private var file: File? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_edit_config) - - editText = findViewById(R.id.editConfig) as EditText - path = "${applicationContext.filesDir.absolutePath}/config" - file = File(path!!) - var content: String - - if (!file!!.exists()) { - Log.e("Baresip", "Failed to find config file") - content = "No config" - } else { - Log.e("Baresip", "Found config file") - val length = file!!.length().toInt() - val bytes = ByteArray(length) - try { - val `in` = FileInputStream(file!!) - try { - `in`.read(bytes) - } finally { - `in`.close() - } - content = String(bytes) - } catch (e: java.io.IOException) { - Log.e("Baresip", "Failed to read config file: " + e.toString()) - content = "Failed to read account file" - } - - } - - Log.d("Baresip", "Content length is: " + content.length) - - editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, - resources.getDimension(R.dimen.textsize)) - editText!!.setText(content, TextView.BufferType.EDITABLE) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - super.onCreateOptionsMenu(menu) - val inflater = menuInflater - inflater.inflate(R.menu.check_icon, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - - val i = Intent(this, MainActivity::class.java) - - if (item.itemId == R.id.checkIcon) { - try { - val fOut = FileOutputStream(file!!.absoluteFile, false) - val fWriter = OutputStreamWriter(fOut) - val res = editText!!.text.toString() - try { - fWriter.write(res) - fWriter.close() - fOut.close() - } catch (e: java.io.IOException) { - Log.e("Baresip", "Failed to write config file: " + e.toString()) - } - - } catch (e: java.io.FileNotFoundException) { - Log.e("Baresip", "Failed to find config file: " + e.toString()) - } - - Log.d("Baresip", "Updated config file") - setResult(RESULT_OK, i) - finish() - return true - - } else if (item.itemId == android.R.id.home) { - - Log.d("Baresip", "Back array was pressed at Edit Config") - setResult(Activity.RESULT_CANCELED, i) - finish() - return true - - } else return super.onOptionsItemSelected(item) - - } -} diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 7475d4c9..a63769d6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -486,7 +486,7 @@ class MainActivity : AppCompatActivity() { callTitle.text = "Incoming call from ..." else callTitle.text = "Outgoing call to ..." - if (acc.mediaenc == "") { + if (acc.mediaEnc == "") { securityButton.visibility = View.INVISIBLE } else { securityButton.setImageResource(R.drawable.box_red) @@ -819,6 +819,11 @@ class MainActivity : AppCompatActivity() { R.color.colorPrimary)) return true } + R.id.config -> { + i = Intent(this, ConfigActivity::class.java) + startActivityForResult(i, CONFIG_CODE) + return true + } R.id.accounts -> { i = Intent(this, AccountsActivity::class.java) val b = Bundle() @@ -827,11 +832,6 @@ class MainActivity : AppCompatActivity() { startActivityForResult(i, ACCOUNTS_CODE) return true } - R.id.config -> { - i = Intent(this, EditConfigActivity::class.java) - startActivityForResult(i, EDIT_CONFIG_CODE) - return true - } R.id.about -> { Api.cmd_exec("audio_debug") i = Intent(this, AboutActivity::class.java) @@ -901,15 +901,12 @@ class MainActivity : AppCompatActivity() { Contact.contacts().map{Contact -> Contact.name})) } - EDIT_CONFIG_CODE -> { - if (resultCode == RESULT_OK) { + CONFIG_CODE -> { + if (resultCode == RESULT_OK) Utils.alertView(this, "Notice", "You need to restart baresip in order to activate saved config!") - Api.reload_config() - } - if (resultCode == RESULT_CANCELED) { - Log.d("Baresip", "Edit config canceled") - } + if (resultCode == RESULT_CANCELED) + Log.d("Baresip", "Config canceled") } CALLS_CODE -> { @@ -1078,7 +1075,7 @@ class MainActivity : AppCompatActivity() { "connected" -> { securityButton.setImageResource(call.security) setSecurityButtonTag(securityButton, call.security) - if ((ua.account.mediaenc == "zrtp") || (ua.account.mediaenc == "dtls_srtpf")) + if ((ua.account.mediaEnc == "zrtp") || (ua.account.mediaEnc == "dtls_srtpf")) securityButton.visibility = View.VISIBLE else securityButton.visibility = View.INVISIBLE @@ -1109,7 +1106,7 @@ class MainActivity : AppCompatActivity() { const val ACCOUNTS_CODE = 1 const val CONTACTS_CODE = 2 - const val EDIT_CONFIG_CODE = 3 + const val CONFIG_CODE = 3 const val CALLS_CODE = 4 const val ABOUT_CODE = 5 const val ACCOUNT_CODE = 6 diff --git a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt index fb811d78..c00a8ea6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt @@ -6,18 +6,25 @@ import android.content.Intent import android.os.Bundle import android.util.Log +import java.io.File + class RunOnStartup : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if ((intent.action == Intent.ACTION_BOOT_COMPLETED) or (intent.action == "com.tutpro.baresip.Restart")) { - Log.d("Baresip", "Start baresip upon boot completed or restart") - val i = Intent(context, MainActivity::class.java) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val b = Bundle() - b.putBoolean("onStartup", true) - i.putExtras(b) - context.startActivity(i) + val configFile = File(context.filesDir.absolutePath + "/config") + val config = Utils.getFileContents(configFile) + val asCv = Utils.getNameValue(config,"auto_start") + if ((asCv.size > 0) && (asCv[0] == "yes")) { + Log.d("Baresip", "Start baresip upon boot completed or restart") + val i = Intent(context, MainActivity::class.java) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val b = Bundle() + b.putBoolean("onStartup", true) + i.putExtras(b) + context.startActivity(i) + } } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt index 34a6ed62..175fd23b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt @@ -36,7 +36,7 @@ class UserAgent (val uap: String) { fun remove(ua: UserAgent) { val index = BaresipService.uas.indexOf(ua) if (index != -1) { - BaresipService.uas[index].destroy() + BaresipService.uas.removeAt(index) BaresipService.status.removeAt(index) } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index cee363ce..3078c59b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -18,7 +18,6 @@ object Utils { Log.e("Baresip", "Failed to find file: " + file.path) return "" } else { - Log.e("Baresip", "Found file: " + file.path) val length = file.length().toInt() val bytes = ByteArray(length) try { @@ -56,6 +55,23 @@ object Utils { } + fun getNameValue(string: String, name: String): ArrayList { + val lines = string.split("\n") + val result = ArrayList() + for (line in lines) { + if (line.startsWith(name)) + result.add((line.substring(name.length).trim()).split(" \t")[0]) + } + return result + } + + fun removeLinesStartingWithName(string: String, name: String): String { + var result = "" + for (line in string.split("\n")) + if (!line.startsWith(name)) result += line + "\n" + return result + } + fun copyAssetToFile(context: Context, asset: String, path: String) { try { val `is` = context.assets.open(asset) @@ -131,35 +147,49 @@ object Utils { fun checkDomain(domain: String): Boolean { val parts = domain.split(".") for (p in parts) { - if (p.length == 0 || p.endsWith("-") || !Regex("^[a-zA-z]([-]|[a-zA-Z0-9])+\$").matches(p)) + if ((p.length == 0) || p.endsWith("-") || + !Regex("^[a-zA-z]([-a-zA-Z0-9])*\$").matches(p)) return false } return true } - fun checkPort(p: String) : Boolean { - val number = p.toIntOrNull() + fun checkPort(port: String): Boolean { + val number = port.toIntOrNull() if (number == null) return false - return (number > 0) and (number < 65536) + return (number > 0) && (number < 65536) } - fun checkHostPort(hp: String) : Boolean { + fun checkHostPort(hp: String, portMandatory: Boolean) : Boolean { val parts = hp.split(":") + if (portMandatory && (parts.size != 2)) return false if (parts.size == 1) return checkIP(parts[0]) || checkDomain(parts[0]) return checkPort(parts[1]) && (checkIP(parts[0]) || checkDomain(parts[0])) } - fun checkParams(p: String) : Boolean { - /* todo: proper check */ + fun checkParams(params: String): Boolean { + for (param in params.split(";")) + if (!checkParam(param)) return false return true } + fun checkParam(param: String): Boolean { + val nameValue = param.split("=") + if (nameValue.size == 1) + /* Todo: do proper check */ + return true + if (nameValue.size == 2) + /* Todo: do proper check */ + return true + return false + } + fun checkHostPortParams(hpp: String) : Boolean { val restParams = hpp.split(";", limit = 2) if (restParams.size == 1) - return checkHostPort(restParams[0]) + return checkHostPort(restParams[0], false) else - return checkHostPort(restParams[0]) && checkParams(restParams[1]) + return checkHostPort(restParams[0], false) && checkParams(restParams[1]) } fun checkSipUri(uri: String): Boolean { @@ -186,15 +216,9 @@ object Utils { return checkHostPortParams(uri.substring(4)) } - fun checkPrintASCII(s: String): Boolean { + fun checkPrintAscii(s: String): Boolean { if (s == "") return true - val printASCIIRegex = Regex("^[ -~]*\$") - return printASCIIRegex.matches(s) - } - - fun checkUint(s: String): Boolean { - val uintRegex = Regex("^[0-9]+\$") - return uintRegex.matches(s) + return Regex("^[ -~]*\$").matches(s) } fun checkName(name: String): Boolean { diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml index d73cf826..3324d744 100644 --- a/app/src/main/res/layout/activity_account.xml +++ b/app/src/main/res/layout/activity_account.xml @@ -98,10 +98,10 @@ android:hint="SIP URI of another Proxy Server" android:textSize="18sp" android:inputType="textEmailAddress" - android:layout_width="fill_parent"> + android:layout_width="fill_parent" > - @@ -109,6 +109,7 @@ android:id="@+id/RegTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentStart="true" android:textSize="18sp" android:textColor="@android:color/black" android:onClick="onClick" @@ -118,10 +119,56 @@ android:id="@+id/Register" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="" + android:layout_alignParentEnd="true" + android:layout_gravity="end" android:checked="false" > - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_edit_accounts.xml b/app/src/main/res/layout/activity_edit_accounts.xml deleted file mode 100644 index 96dd5793..00000000 --- a/app/src/main/res/layout/activity_edit_accounts.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_edit_config.xml b/app/src/main/res/layout/activity_edit_config.xml deleted file mode 100644 index 95a95a68..00000000 --- a/app/src/main/res/layout/activity_edit_config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 3f19c822..76c3af9a 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -1,11 +1,11 @@ - - + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34d50319..bd3372d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,9 +5,8 @@ Baresip based Android application\n\n Juha Heinanen jh@tutpro.com\n\n Usage Hints\n\n - - start by creating an account (click item titles for help)\n - - usually there is no need to edit the config\n - - in config, dns_server by default points to 8.8.8.8:53 for Android 8+ usage\n + - check that config default values meet your needs (click item titles for help)\n + - then create one or more accounts (again click item titles for help)\n - peers of calls and messages can be added to contacts by long clicks\n - long clicks can also be used to remove calls, chats, messages, and contacts\n - call click when callee has not been given can be used for re-dial\n @@ -25,6 +24,13 @@ If checked, registration is enabled and REGISTER requests are sent at 12 minute intervals. + If checked, ICE (Interactive Connectivity Establishment) is enabled for NAT + traversal. + + A specific Stun Server used by ICE of form host[:port]. + Factory default value is \'stun.l.google.com:19302\', pointing to public Google STUN server. + Username and password are currently not supported. + List of supported audio codecs in priority order. Selects media transport encryption protocol.\n ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried after @@ -41,5 +47,10 @@ SIP URI for checking of voicemail messages. If left empty, voicemail messages (Message Waiting Indications) are not subscribed to. > - MessageActivity - + If checked, baresip starts automatically after device (re)start. + Comma separated list of DNS servers. Each DNS server is of form + server:port. Factory default value is \'8.8.8.8:53\' pointing to public Google DNS server. + Average maximum bit rate used by Opus audio stream. + Valid value are 6000-510000. Factory default is 28000. + If checked, ICE Lite Mode is used. + \ No newline at end of file diff --git a/fdroid/repo/com.tutpro.baresip/en-US/changelogs/5.0.0.txt b/fdroid/repo/com.tutpro.baresip/en-US/changelogs/5.0.0.txt new file mode 100644 index 00000000..1c8caf31 --- /dev/null +++ b/fdroid/repo/com.tutpro.baresip/en-US/changelogs/5.0.0.txt @@ -0,0 +1,6 @@ +- Replaced text mode EditConfigActivity with graphical Config activity +- Current configuration items are Auto Start, DNS Servers, Opus Bit Rate, + and ICE Lite +- Added ICE and Stun Server account options +- Fixed bug in deleting of an account +- Updated libbaresip.a and baresip.h to latest baresip master