diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c index 599a8529..4da751c8 100644 --- a/app/src/main/cpp/baresip.c +++ b/app/src/main/cpp/baresip.c @@ -354,6 +354,8 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc LOGD("Starting baresip\n"); + char start_error[64] = ""; + BaresipContext *pctx = (BaresipContext *)(&g_ctx); JavaVM *javaVM = pctx->javaVM; jint res = (*javaVM)->GetEnv(javaVM, (void **) &env, JNI_VERSION_1_6); @@ -382,12 +384,14 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc err = conf_configure(); if (err) { LOGW("conf_configure() failed: (%d)\n", err); + strcpy(start_error, "conf_configure"); goto out; } err = baresip_init(conf_config()); if (err) { LOGW("baresip_init() failed (%d)\n", err); + strcpy(start_error, "baresip_init"); goto out; } @@ -397,6 +401,7 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc true, true, true); if (err) { LOGE("ua_init() failed (%d)\n", err); + strcpy(start_error, "ua_init"); goto out; } @@ -405,18 +410,21 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc err = uag_event_register(ua_event_handler, NULL); if (err) { LOGE("uag_event_register() failed (%d)\n", err); + strcpy(start_error, "uag_event_register"); goto out; } err = message_listen(baresip_message(), message_handler, NULL); if (err) { LOGE("message_listen() failed (%d)\n", err); + strcpy(start_error, "message_listen"); goto out; } err = conf_modules(); if (err) { LOGW("conf_modules() failed (%d)\n", err); + strcpy(start_error, "conf_modules"); goto out; } @@ -436,12 +444,20 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc LOGI("allocating mqeue\n"); err = mqueue_alloc(&mq, mqueue_handler, NULL); - if (err) + if (err) { + LOGW("mqueue_alloc failed (%d)\n", err); + strcpy(start_error, "mqueue_alloc"); goto out; + } /* char debug_buf[2048]; int l; l = re_snprintf(&(debug_buf[0]), 2047, "%H", net_debug, baresip_network()); + if (l != -1) { + debug_buf[l] = '\0'; + LOGD("%s\n", debug_buf); + } + l = re_snprintf(&(debug_buf[0]), 2047, "%H", ua_print_sip_status); if (l != -1) { debug_buf[l] = '\0'; LOGD("%s\n", debug_buf); @@ -481,8 +497,11 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc stopped: LOGD("tell main that baresip has stopped"); - jmethodID stoppedId = (*env)->GetMethodID(env, pctx->mainActivityClz, "stopped", "()V"); - (*env)->CallVoidMethod(env, pctx->mainActivityObj, stoppedId); + jstring javaError = (*env)->NewStringUTF(env, start_error); + jmethodID stoppedId = (*env)->GetMethodID(env, pctx->mainActivityClz, "stopped", + "(Ljava/lang/String;)V"); + (*env)->CallVoidMethod(env, pctx->mainActivityObj, stoppedId, javaError); + (*env)->DeleteLocalRef(env, javaError); return; } @@ -960,7 +979,7 @@ Java_com_tutpro_baresip_Api_ua_1destroy(JNIEnv *env, jobject thiz, jstring javaU struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10); LOGD("destroying ua %s\n", native_ua); (*env)->ReleaseStringUTFChars(env, javaUA, native_ua); - ua_destroy(ua); + (void)ua_destroy(ua); } JNIEXPORT jstring JNICALL diff --git a/app/src/main/kotlin/com/tutpro/baresip/Account.kt b/app/src/main/kotlin/com/tutpro/baresip/Account.kt index 397e9b4c..0f513320 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Account.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Account.kt @@ -127,6 +127,7 @@ class Account(val accp: String) { fun host() : String { return aor.split("@")[1] } + companion object { fun accounts(): ArrayList { diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index bdc0ab1b..15e69969 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -23,13 +23,13 @@ import android.provider.Settings import java.io.File import java.nio.charset.StandardCharsets -import java.io.InputStream import java.util.* import kotlin.math.roundToInt class BaresipService: Service() { private val LOG_TAG = "Baresip Service" + internal lateinit var intent: Intent internal lateinit var am: AudioManager internal lateinit var rt: Ringtone @@ -39,7 +39,6 @@ class BaresipService: Service() { internal lateinit var fl: WifiManager.WifiLock internal var rtTimer: Timer? = null - internal var filesPath = "" internal var audioFocusRequest: AudioFocusRequest? = null internal var audioFocused = false internal var origCallVolume = 0 @@ -51,7 +50,8 @@ class BaresipService: Service() { intent = Intent("com.tutpro.baresip.EVENT") intent.setPackage("com.tutpro.baresip") - filesPath = applicationContext.filesDir.absolutePath + filesPath = filesDir.absolutePath + context = applicationContext am = getSystemService(Context.AUDIO_SERVICE) as AudioManager val rtUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext, @@ -129,49 +129,7 @@ class BaresipService: Service() { Utils.copyAssetToFile(applicationContext, a, "$filesPath/$a") } else { Log.d(LOG_TAG, "Asset $a already copied") - if (a == "config") { - val inputStream: InputStream = file.inputStream() - var contents = inputStream.bufferedReader().use { it.readText() } - inputStream.close() - var write = false - if (!contents.contains("zrtp_hash")) { - contents = "${contents}zrtp_hash yes\n" - write = true - } - if (contents.contains(Regex("#module_app[ ]+mwi.so"))) { - contents = contents.replace(Regex("#module_app[ ]+mwi.so"), - "module_app mwi.so") - write = true - } - if (!contents.contains("opus_application")) { - contents = "${contents}opus_application voip\n" - write = true - } - if (!contents.contains("log_level")) { - contents = "${contents}log_level 2\n" - Api.log_level_set(2) - Log.logLevel = Log.LogLevel.WARN - write = true - } else { - val ll = Utils.getNameValue(contents, "log_level")[0].toInt() - Api.log_level_set(ll) - Log.logLevelSet(ll) - } - if (!contents.contains("prefer_ipv6")) { - contents = "prefer_ipv6 no\n${contents}" - write = true - } - if (!contents.contains("call_volume")) { - contents = "${contents}call_volume 0\n" - write = true - } else { - callVolume = Utils.getNameValue(contents, "call_volume")[0].toInt() - } - if (write) { - Log.d(LOG_TAG, "Writing '$contents'") - Utils.putFileContents(file, contents) - } - } + if (a == "config") Config.initialize() } } ContactsActivity.restoreContacts(applicationContext.filesDir) @@ -632,12 +590,13 @@ class BaresipService: Service() { } @Keep - fun stopped() { - Log.d(LOG_TAG, "'stopped' from baresip") + fun stopped(error: String) { + Log.d(LOG_TAG, "'stopped' from baresip with error $error") isServiceRunning = false + if (error == "ua_init") Config.remove("sip_listen") val intent = Intent("service event") intent.putExtra("event", "stopped") - intent.putExtra("params", arrayListOf()) + intent.putExtra("params", arrayListOf(error)) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) stopForeground(true) stopSelf() @@ -807,6 +766,8 @@ class BaresipService: Service() { var speakerPhone = false var callVolume = 0 + var filesPath = "" + var context: Context? = null var uas = ArrayList() var status = ArrayList() var calls = ArrayList() diff --git a/app/src/main/kotlin/com/tutpro/baresip/Config.kt b/app/src/main/kotlin/com/tutpro/baresip/Config.kt new file mode 100644 index 00000000..d34eb419 --- /dev/null +++ b/app/src/main/kotlin/com/tutpro/baresip/Config.kt @@ -0,0 +1,87 @@ +package com.tutpro.baresip + +import java.io.File + +object Config { + + private val path = BaresipService.filesPath + "/config" + private val file = File(path) + private val context = BaresipService.context + + private var config = Utils.getFileContents(file) + + fun initialize() { + var write = false + if (!config.contains("zrtp_hash")) { + config = "${config}zrtp_hash yes\n" + write = true + } + if (config.contains(Regex("#module_app[ ]+mwi.so"))) { + config = config.replace(Regex("#module_app[ ]+mwi.so"), + "module_app mwi.so") + write = true + } + if (!config.contains("opus_application")) { + config = "${config}opus_application voip\n" + write = true + } + if (!config.contains("log_level")) { + config = "${config}log_level 2\n" + Api.log_level_set(2) + Log.logLevel = Log.LogLevel.WARN + write = true + } else { + val ll = variable("log_level")[0].toInt() + Api.log_level_set(ll) + Log.logLevelSet(ll) + } + if (!config.contains("prefer_ipv6")) { + config = "prefer_ipv6 no\n${config}" + write = true + } + if (!config.contains("call_volume")) { + config = "${config}call_volume 0\n" + write = true + } else { + BaresipService.callVolume = variable("call_volume")[0].toInt() + } + if (write) { + Log.e("Baresip", "Writing '$config'") + Utils.putFileContents(file, config) + } + } + + fun variable(name: String): ArrayList { + val lines = config.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 add(variable: String, value: String) { + config += "\n$variable $value\n" + } + + fun remove(variable: String) { + config = Utils.removeLinesStartingWithName(config, variable) + Utils.putFileContents(file, config) + } + + fun replace(variable: String, value: String) { + remove(variable) + add(variable, value) + } + + fun reset() { + Utils.copyAssetToFile(context!!, "config", path) + } + + fun save() { + Utils.putFileContents(file, config) + Log.d("Baresip", "New config '$config'") + // Api.reload_config() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt index 2404277d..1d12aa5c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -9,12 +9,10 @@ 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 listenAddr: EditText internal lateinit var preferIPv6: CheckBox internal lateinit var dnsServers: EditText internal lateinit var opusBitRate: EditText @@ -23,6 +21,7 @@ class ConfigActivity : AppCompatActivity() { internal lateinit var reset: CheckBox private var oldAutoStart = "" + private var oldListenAddr = "" private var oldPreferIPv6 = "" private var oldDnsServers = "" private var oldOpusBitrate = "" @@ -38,38 +37,35 @@ class ConfigActivity : AppCompatActivity() { 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") + val asCv = Config.variable("auto_start") oldAutoStart = if (asCv.size == 0) "no" else asCv[0] autoStart.isChecked = oldAutoStart == "yes" + listenAddr = findViewById(R.id.ListenAddress) as EditText + val laCv = Config.variable("sip_listen") + oldListenAddr = if (laCv.size == 0) "" else laCv[0] + listenAddr.setText(oldListenAddr) + preferIPv6 = findViewById(R.id.PreferIPv6) as CheckBox - val piCv = Utils.getNameValue(config, "prefer_ipv6") + val piCv = Config.variable("prefer_ipv6") oldPreferIPv6 = if (piCv.size == 0) "no" else piCv[0] preferIPv6.isChecked = oldPreferIPv6 == "yes" dnsServers = findViewById(R.id.DnsServers) as EditText - val dsCv = Utils.getNameValue(config, "dns_server") + val dsCv = Config.variable("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") + val obCv = Config.variable("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") + val imCv = Config.variable("ice_mode") oldIceMode = if (imCv.size == 0) "full" else imCv[0] iceLite.isChecked = oldIceMode == "lite" @@ -95,7 +91,7 @@ class ConfigActivity : AppCompatActivity() { } debug = findViewById(R.id.Debug) as CheckBox - val dbCv = Utils.getNameValue(config, "log_level") + val dbCv = Config.variable("log_level") if (dbCv.size == 0) oldLogLevel = "2" else @@ -123,17 +119,28 @@ class ConfigActivity : AppCompatActivity() { var autoStartString = "no" if (autoStart.isChecked) autoStartString = "yes" if (oldAutoStart != autoStartString) { - config = Utils.removeLinesStartingWithName(config, "auto_start") - config += "\nauto_start $autoStartString\n" + Config.replace("auto_start", autoStartString) save = true restart = false } + val listenAddr = listenAddr.text.toString().trim() + if (listenAddr != oldListenAddr) { + if ((listenAddr != "") && !Utils.checkIpPort(listenAddr)) { + Utils.alertView(this, "Notice", + "Invalid Listen Address '$listenAddr'") + return false + } + Config.remove("sip_listen") + if (listenAddr != "") Config.add("sip_listen", listenAddr) + save = true + restart = true + } + var preferIPv6String = "no" if (preferIPv6.isChecked) preferIPv6String = "yes" if (oldPreferIPv6 != preferIPv6String) { - config = Utils.removeLinesStartingWithName(config, "prefer_ipv6") - config = "prefer_ipv6 $preferIPv6String\n$config" + Config.replace("prefer_ipv6", preferIPv6String) save = true restart = true } @@ -144,9 +151,9 @@ class ConfigActivity : AppCompatActivity() { Utils.alertView(this, "Notice", "Invalid DNS Servers: $dnsServers") return false } - config = Utils.removeLinesStartingWithName(config, "dns_server") + Config.remove("dns_server") for (server in dnsServers.split(",")) - config += "\ndns_server ${server.trim()}\n" + Config.add("dns_server", server) save = true restart = true } @@ -157,8 +164,7 @@ class ConfigActivity : AppCompatActivity() { Utils.alertView(this, "Notice", "Invalid Opus Bit Rate: $opusBitRate") return false } - config = Utils.removeLinesStartingWithName(config, "opus_bitrate") - config += "\nopus_bitrate $opusBitRate\n" + Config.replace("opus_bitrate", opusBitRate) save = true restart = true } @@ -166,48 +172,33 @@ class ConfigActivity : AppCompatActivity() { var iceModeString = "full" if (iceLite.isChecked) iceModeString = "lite" if (oldIceMode != iceModeString) { - config = Utils.removeLinesStartingWithName(config, "ice_mode") - config += "\nice_mode $iceModeString\n" + Config.replace("ice_mode", iceModeString) save = true restart = true } if (BaresipService.callVolume != callVolume) { BaresipService.callVolume = callVolume - config = Utils.removeLinesStartingWithName(config, "call_volume") - config += "\ncall_volume $callVolume\n" + Config.replace("call_volume", callVolume.toString()) save = true } var logLevelString = "2" if (debug.isChecked) logLevelString = "0" if (oldLogLevel != logLevelString) { - config = Utils.removeLinesStartingWithName(config, "log_level") - config += "\nlog_level $logLevelString\n" + Config.replace("log_level", logLevelString) Api.log_level_set(logLevelString.toInt()) Log.logLevelSet(logLevelString.toInt()) save = true } if (reset.isChecked) { - Utils.copyAssetToFile(applicationContext, "config", - applicationContext.filesDir.absolutePath + "/config") + Config.reset() save = false restart = 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" - } - Log.d("Baresip", "New config '$newConfig'") - Utils.putFileContents(configFile, newConfig) - // Api.reload_config() - } + if (save) Config.save() intent.putExtra("restart", restart ) setResult(RESULT_OK, intent) @@ -229,6 +220,9 @@ class ConfigActivity : AppCompatActivity() { findViewById(R.id.AutoStartTitle) as TextView-> { Utils.alertView(this, "Start Automatically", getString(R.string.autoStart)) } + findViewById(R.id.ListenAddressTitle) as TextView-> { + Utils.alertView(this, "Listen Address", getString(R.string.listenAddress)) + } findViewById(R.id.PreferIPv6Title) as TextView-> { Utils.alertView(this, "Prefer IPv6", getString(R.string.preferIPv6)) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 2f314ed6..7e4563b9 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -558,11 +558,25 @@ class MainActivity : AppCompatActivity() { return } if (event == "stopped") { - Log.d("Baresip", "Handling service event 'stopped'") - quitTimer.cancel() - finishAndRemoveTask() - System.exit(0) - return + Log.d("Baresip", "Handling service event 'stopped' with param ${params[0]}") + if (params[0] != "") { + val alertDialog = AlertDialog.Builder(this).create() + alertDialog.setTitle("Notice") + alertDialog.setMessage("Baresip failed to start! Listen Address was reset. Restart baresip.") + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK" + ) { dialog, _ -> + dialog.dismiss() + quitTimer.cancel() + finishAndRemoveTask() + System.exit(0) + } + alertDialog.show() + } else { + quitTimer.cancel() + finishAndRemoveTask() + System.exit(0) + return + } } val uap = params[0] val ua = UserAgent.find(uap) diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index 967b45fb..b8b448b8 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -153,14 +153,18 @@ object Utils { return Regex("^[+]?[0-9]{1,16}\$").matches(no) } - fun checkIP(ip: String): Boolean { + fun checkIpV4(ip: String): Boolean { return Regex("^(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$").matches(ip) } - fun checkIPv6(ip: String): Boolean { + fun checkIpV6(ip: String): Boolean { return Regex("^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$").matches(ip) } + fun checkIp(ip: String): Boolean { + return checkIpV4(ip) || checkIpV6(ip) + } + fun checkUriUser(user: String): Boolean { for (c in user) if (!(c.isLetterOrDigit() || c in "-_.!~*'()&=+$,;?/")) return false @@ -183,23 +187,27 @@ object Utils { return (number > 0) && (number < 65536) } - fun checkHostPort(hp: String, portMandatory: Boolean) : Boolean { - if (hp.startsWith("[")) { - val parts = hp.split("]") - if (parts.size != 2) return false - Log.d("Baresip", "Checking IPv6 '${parts[0].substring(1)}'") - if (!checkIPv6(parts[0].substring(1))) return false - if (portMandatory && !parts[1].startsWith(":")) return false - Log.d("Baresip", "Checking port '${parts[1].substring(1)}'") - return checkPort(parts[1].substring(1)) + fun checkHost(host: String): Boolean { + return checkIp(host) || checkDomain(host) + } + + fun checkHostPort(hostPort: String, portMandatory: Boolean): Boolean { + if (portMandatory) { + return checkHost(hostPort.substringBeforeLast(":")) && + checkPort(hostPort.substringAfterLast(":")) } else { - 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])) + if (hostPort.substringAfterLast(":").contains(Regex("^[0-9]+\$"))) + return checkHostPort(hostPort, true) + else + return checkHost(hostPort) } } + fun checkIpPort(ipPort: String): Boolean { + return checkIp(ipPort.substringBeforeLast(":")) && + checkPort(ipPort.substringAfterLast(":")) + } + fun checkParams(params: String): Boolean { for (param in params.split(";")) if (!checkParam(param)) return false @@ -241,7 +249,7 @@ object Utils { val userDomain = uri.replace("sip:", "").split("@") if (userDomain.size != 2) return false if (!checkUriUser(userDomain[0])) return false - return checkDomain(userDomain[1]) || checkIP(userDomain[1]) + return checkDomain(userDomain[1]) || checkIp(userDomain[1]) } fun checkPrintAscii(s: String): Boolean { diff --git a/app/src/main/res/layout/activity_config.xml b/app/src/main/res/layout/activity_config.xml index 430cefb6..7d72048b 100644 --- a/app/src/main/res/layout/activity_config.xml +++ b/app/src/main/res/layout/activity_config.xml @@ -42,6 +42,27 @@ + + + + + + If checked, this account is selected when baresip is started. > If checked, baresip starts automatically after device (re)start. + IP address and port of form \'address:port\' at which baresip listens + for incoming SIP requests. If IP address is an IPv6 address, it must be written inside + brackets []. IPv4 address 0.0.0.0 or IPv6 address [::] makes baresip listen at all + available addresses. If left empty (factory default), baresip listens at port 5060 of + all available addresses. + Prefer IPv6 if both IPv4 and IPv6 are available. Comma separated list of DNS servers. Each DNS server is of form \'server:port\'. If server is an IPv6 address, the address must be written inside brackets [].