added graphical account configuration and many other improvements

This commit is contained in:
Juha Heinanen
2018-05-23 10:58:57 +03:00
parent 784dd2feb5
commit f4bd1a93ed
27 changed files with 1635 additions and 418 deletions

View File

@ -35,7 +35,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:25.2.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()

View File

@ -51,8 +51,18 @@
</activity>
<activity
android:name=".EditAccountsActivity"
android:label="Edit Accounts"
android:name=".AccountsActivity"
android:label="Accounts"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
</activity>
<activity
android:name=".AccountActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="Account"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"

View File

@ -31,4 +31,3 @@
# <sip:user@1.2.3.4;auth_pass=secret;transport=tcp>
# <sip:user@[2001:df8:0:16:216:6fff:fe91:614c]:5070;transport=tcp;auth_pass=secret>
#
<sip:foo@test.tutpro.com>;auth_user=foo;auth_pass=xxx;outbound="sip:192.168.43.98:5060;transport=tcp";ptime=20;audio_codecs=OPUS/48000/2,PCMU,PCMA;video_codecs="";regint=600;pubint=0;sipnat=outbound

View File

@ -128,9 +128,9 @@ module ice.so
#module natpmp.so
# Media encryption modules
#module srtp.so
#module dtls_srtp.so
#module zrtp.so
module srtp.so
module dtls_srtp.so
module zrtp.so
#------------------------------------------------------------------------------

View File

@ -102,7 +102,12 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev,
re_snprintf(event_buf, sizeof event_buf, "%s", "call incoming");
break;
case UA_EVENT_CALL_MENC:
re_snprintf(event_buf, sizeof event_buf, "%s", prm);
if (prm[0] == '1')
re_snprintf(event_buf, sizeof event_buf, "call verify,%s", prm+2);
else if (prm[0] == '2')
re_snprintf(event_buf, sizeof event_buf, "call verified,%s", prm+2);
else
re_snprintf(event_buf, sizeof event_buf, "%s", "unknown menc event");
break;
case UA_EVENT_CALL_CLOSED:
re_snprintf(event_buf, sizeof event_buf, "%s", "call closed");
@ -317,26 +322,342 @@ Java_com_tutpro_baresip_MainActivity_baresipStop(JNIEnv *env, jobject thiz)
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1aor(JNIEnv *env, jobject thiz, jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
Java_com_tutpro_baresip_AccountKt_account_1display_1name(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);
if (strlen(native_ua) > 0)
return (*env)->NewStringUTF(env, ua_aor(ua));
(*env)->ReleaseStringUTFChars(env, javaAcc, native_acc);
if (acc) {
const char *dn = account_display_name(acc);
if (dn)
return (*env)->NewStringUTF(env, dn);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1display_1name(JNIEnv *env, jobject thiz,
jstring javaAcc, jstring javaDn) {
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 *dn = (*env)->GetStringUTFChars(env, javaDn, 0);\
int res;
if (strlen(dn) > 0)
res = account_set_display_name(acc, dn);
else
res = account_set_display_name(acc, NULL);
(*env)->ReleaseStringUTFChars(env, javaDn, dn);
return res;
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_AccountKt_account_1aor(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)
return (*env)->NewStringUTF(env, account_aor(acc));
else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1mediaenc(JNIEnv *env, jobject thiz, jstring javaUA)
Java_com_tutpro_baresip_AccountKt_account_1auth_1user(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 *au = account_auth_user(acc);
if (au)
return (*env)->NewStringUTF(env, au);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1auth_1user(JNIEnv *env, jobject thiz,
jstring javaAcc, jstring javaUser) {
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 *user = (*env)->GetStringUTFChars(env, javaUser, 0);
int res;
if (strlen(user) > 0)
res = account_set_auth_user(acc, user);
else
res = account_set_auth_user(acc, NULL);
(*env)->ReleaseStringUTFChars(env, javaUser, user);
return res;
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_AccountKt_account_1auth_1pass(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 *ap = account_auth_pass(acc);
if (ap)
return (*env)->NewStringUTF(env, ap);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1auth_1pass(JNIEnv *env, jobject thiz,
jstring javaAcc, jstring javaPass) {
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 *pass = (*env)->GetStringUTFChars(env, javaPass, 0);
int res;
if (strlen(pass) > 0)
res = account_set_auth_pass(acc, pass);
else
res = account_set_auth_pass(acc, NULL);
(*env)->ReleaseStringUTFChars(env, javaPass, pass);
return res;
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_AccountKt_account_1outbound(JNIEnv *env, jobject thiz, jstring javaAcc,
jint ix) {
const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0);
struct account *acc = (struct account *) strtoul(native_acc, NULL, 10);
const uint16_t native_ix = ix;
const char *outbound;
(*env)->ReleaseStringUTFChars(env, javaAcc, native_acc);
if (acc) {
outbound = account_outbound(acc, native_ix);
if (outbound)
return (*env)->NewStringUTF(env, outbound);
else
return (*env)->NewStringUTF(env, "");
} else {
return (*env)->NewStringUTF(env, "");
}
}
JNIEXPORT int JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1outbound(JNIEnv *env, jobject thiz, jstring javaAcc,
jstring javaOb, jint javaIx) {
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 *ob = (*env)->GetStringUTFChars(env, javaOb, 0);
const uint16_t ix = javaIx;
int res;
if (strlen(ob) > 0)
res = account_set_outbound(acc, ob, ix);
else
res = account_set_outbound(acc, NULL, ix);
(*env)->ReleaseStringUTFChars(env, javaOb, ob);
return res;
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_AccountKt_account_1audio_1codec(JNIEnv *env, jobject thiz, jstring javaAcc,
jint ix) {
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_ix = ix;
const struct list *codecl;
char codec_buf[32];
struct le *le;
if (acc) {
codecl = account_aucodecl(acc);
if (!list_isempty(codecl)) {
int i = -1;
for (le = list_head(codecl); le != NULL; le = le->next) {
i++;
if (i > ix) break;
if (i == ix) {
const struct aucodec *ac = le->data;
re_snprintf(codec_buf, sizeof codec_buf, "%s/%u/%u", ac->name, ac->srate, ac->ch);
break;
}
}
if (i == ix) {
return (*env)->NewStringUTF(env, codec_buf);
}
}
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1audio_1codecs(JNIEnv *env, jobject thiz,
jstring javaAcc, jstring javaCodecs) {
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 *codecs = (*env)->GetStringUTFChars(env, javaCodecs, 0);
LOGD("setting audio codecs '%s'\n", codecs);
int res = account_set_audio_codecs(acc, codecs);
(*env)->ReleaseStringUTFChars(env, javaCodecs, codecs);
return res;
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1regint(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)
return account_regint(acc);
else
return 0;
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1regint(JNIEnv *env, jobject thiz,
jstring javaAcc, jint javaRegint) {
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 uint32_t regint = javaRegint;
return account_set_regint(acc, regint);
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_AccountKt_account_1mediaenc(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 *mediaenc = account_mediaenc(acc);
if (mediaenc) return (*env)->NewStringUTF(env, mediaenc);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_AccountKt_account_1set_1mediaenc(JNIEnv *env, jobject thiz,
jstring javaAcc, jstring javaMencid) {
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 *mencid = (*env)->GetStringUTFChars(env, javaMencid, 0);
int res;
if (strlen(mencid) > 0)
res = account_set_mediaenc(acc, mencid);
else
res = account_set_mediaenc(acc, NULL);
(*env)->ReleaseStringUTFChars(env, javaMencid, mencid);
return res;
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1alloc(JNIEnv *env, jobject thiz,
jstring javaSipUri) {
const char *sip_uri = (*env)->GetStringUTFChars(env, javaSipUri, 0);
struct ua *ua;
LOGD("allocating UA '%s'\n", sip_uri);
int res = ua_alloc(&ua, sip_uri);
(*env)->ReleaseStringUTFChars(env, javaSipUri, sip_uri);
if (res == 0) {
char ua_buf[64];
UpdateContext *pctx = (UpdateContext*)(&g_ctx);
sprintf(ua_buf, "%lu", (unsigned long)ua);
jstring javaUA = (*env)->NewStringUTF(env, ua_buf);
LOGD("adding account %s/%s\n", ua_aor(ua), ua_buf);
jmethodID accountId = (*env)->GetMethodID(env, pctx->mainActivityClz, "addAccount",
"(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, pctx->mainActivityObj, accountId, javaUA);
(*env)->DeleteLocalRef(env, javaUA);
}
return res;
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1register(JNIEnv *env, jobject thiz,
jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
struct account *acc = ua_account(ua);
char *mediaenc = account_mediaenc(acc);
if (mediaenc)
return (*env)->NewStringUTF(env, mediaenc);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
return ua_register(ua);
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1unregister(JNIEnv *env, jobject thiz,
jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
ua_unregister(ua);
}
JNIEXPORT jboolean JNICALL
Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1isregistered(JNIEnv *env, jobject thiz,
jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
return ua_isregistered(ua);
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1destroy(JNIEnv *env, jobject thiz,
jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
mem_deref(ua);
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_UserAgentKt_ua_1account(JNIEnv *env, jobject thiz, jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
struct account *acc;
char acc_buf[256];
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
acc_buf[0] = '\0';
if (ua) {
acc = ua_account(ua);
if (acc) sprintf(acc_buf, "%lu", (unsigned long) acc);
}
return (*env)->NewStringUTF(env, acc_buf);
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_UserAgentKt_ua_1aor(JNIEnv *env, jobject thiz, jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
if (ua)
return (*env)->NewStringUTF(env, ua_aor(ua));
else
return (*env)->NewStringUTF(env, "");
}
@ -423,7 +744,7 @@ Java_com_tutpro_baresip_MainActivity_ua_1answer(JNIEnv *env, jobject thiz,
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *env, jobject thiz,
jstring javaCall) {
jstring javaCall) {
const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0);
LOGD("holding call %s\n", native_call);
int res = call_hold(ua_call(uag_current()), true);
@ -433,7 +754,7 @@ Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *env, jobject thiz,
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_MainActivity_call_1unhold(JNIEnv *env, jobject thiz,
jstring javaCall) {
jstring javaCall) {
const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0);
LOGD("holding call %s\n", native_call);
int res = call_hold(ua_call(uag_current()), false);
@ -514,10 +835,50 @@ Java_com_tutpro_baresip_MainActivity_reload_1config(JNIEnv *env, jobject thiz) {
}
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_MainActivity_cmd_1exec(JNIEnv *env, jobject thiz, jstring javaCmd) {
Java_com_tutpro_baresip_Utils_cmd_1exec(JNIEnv *env, jobject thiz, jstring javaCmd) {
const char *native_cmd = (*env)->GetStringUTFChars(env, javaCmd, 0);
LOGD("processing command '%s'\n", native_cmd);
int res = cmd_process_long(baresip_commands(), native_cmd, strlen(native_cmd), &pf_null, NULL);
(*env)->ReleaseStringUTFChars(env, javaCmd, native_cmd);
return res;
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_Utils_audio_1codecs(JNIEnv *env, jobject thiz)
{
struct list *aucodecl = baresip_aucodecl();
struct le *le;
char codec_buf[256];
char *start = &(codec_buf[0]);
unsigned int left = sizeof codec_buf;
int len;
for (le = list_head(aucodecl); le != NULL; le = le->next) {
const struct aucodec *ac = le->data;
if (start == &(codec_buf[0]))
len = re_snprintf(start, left, "%s/%u/%u", ac->name, ac->srate, ac->ch);
else
len = re_snprintf(start, left, ",%s/%u/%u", ac->name, ac->srate, ac->ch);
if (len == -1) {
LOGE("failed to print codec to buffer\n");
codec_buf[0] = '\0';
break;
}
start = start + len;
left = left - len;
}
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT jboolean JNICALL
Java_com_tutpro_baresip_Utils_uri_1decode(JNIEnv *env, jobject thiz, jstring javaUri) {
const char *uri = (*env)->GetStringUTFChars(env, javaUri, 0);
struct pl pl;
struct uri uri2;
LOGD("decoding uri '%s'\n", uri);
pl_set_str(&pl, uri);
int res = uri_decode(&uri2, &pl);
(*env)->ReleaseStringUTFChars(env, javaUri, uri);
return res == 0;
}

View File

@ -1,3 +1,128 @@
package com.tutpro.baresip
class Account(val ua: String, val aoR: String, val mediaenc: String, var status: String) {}
class Account(val accp: String) {
var displayName = account_display_name(accp)
val aor = account_aor(accp)
var authUser = account_auth_user(accp)
var authPass = account_auth_pass(accp)
var outbound = ArrayList<String>()
var audioCodec = ArrayList<String>()
var regint = account_regint(accp)
var mediaenc = account_mediaenc(accp)
init {
var i = 0
while (true) {
val ob = account_outbound(accp, i)
if (ob != "") {
outbound.add(ob)
i++
} else {
break
}
}
i = 0
while (true) {
val ac = account_audio_codec(accp, i)
if (ac != "") {
audioCodec.add(ac)
i++
} else {
break
}
}
}
fun print() : String {
var res: String
if (displayName != "")
res = "\"${displayName}\" "
else
res = ""
res = res + "<${aor}>"
if (authUser != "") res = res + ";auth_user=\"${authUser}\""
if (authPass != "") res = res + ";auth_pass=\"${authPass}\""
if (outbound.size > 0) res = res + ";outbound=\"${outbound[0]}\""
if (outbound.size > 1) res = res + ";outbound2=\"${outbound[1]}\""
if (audioCodec.size > 0) {
var first = true
res = res + ";audio_codecs="
for (c in audioCodec)
if (first) {
res = res + c
first = false
} else {
res = res + ",$c"
}
}
if (mediaenc != "") res = res + ";mediaenc=${mediaenc}"
res = res + ";ptime=20;regint=${regint};regq=0.5;pubint=0;answermode=manual;sipnat=outbound"
return res
}
companion object {
fun accounts(): ArrayList<Account> {
var res = ArrayList<Account>()
for (ua in MainActivity.uas) {
res.add(ua.account)
}
return res
}
fun find(uas: ArrayList<UserAgent>, accp: String): Account? {
for (ua in uas) {
if (ua.account.accp == accp) return ua.account
}
return null
}
fun findUA(aor: String): UserAgent? {
for (ua in MainActivity.uas) {
if (ua.aor == aor) return ua
}
return null
}
fun exists(uas: ArrayList<UserAgent>, aor: String): Boolean {
for (ua in uas) {
if (ua.account.aor == aor) return true
}
return false
}
fun printNew(aor: String) : String {
var res = "<$aor>;auth_user=\"\";auth_pass=\"\";audio_codecs=opus/48000/2,PCMU,PCMA"
res = res + ";ptime=20;regint=600;regq=0.5;pubint=0;answermode=manual;sipnat=outbound"
res = res + ";mediaenc=zrtp"
return res
}
}
}
external fun account_set_display_name(acc: String, dn: String): Int
external fun account_display_name(acc: String): String
external fun account_aor(acc: String): String
external fun account_auth_user(acc: String): String
external fun account_set_auth_user(acc: String, user: String): Int
external fun account_auth_pass(acc: String): String
external fun account_set_auth_pass(acc: String, pass: String): Int
external fun account_outbound(acc: String, ix: Int): String
external fun account_set_outbound(acc: String, ob: String, ix: Int): 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_mediaenc(acc: String): String
external fun account_set_mediaenc(acc: String, mencid: String): Int
external fun account_set_audio_codecs(acc: String, codecs: String): Int

View File

@ -0,0 +1,298 @@
package com.tutpro.baresip
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.LinearLayout.LayoutParams
import android.widget.*
class AccountActivity : AppCompatActivity() {
internal lateinit var acc: Account
internal lateinit var displayName: EditText
internal lateinit var aor: String
internal lateinit var authUser: EditText
internal lateinit var authPass: EditText
internal lateinit var outbound1: EditText
internal lateinit var outbound2: EditText
internal lateinit var regint: EditText
internal lateinit var mediaEnc: String
private var newCodecs = ArrayList<String>()
private var save = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_account)
val b = intent.extras
acc = Account.find(MainActivity.uas, b.getString("accp"))!!
aor = acc.aor
setTitle(aor.replace("sip:", ""))
displayName = findViewById(R.id.DisplayName) as EditText
displayName.setText(acc.displayName)
authUser = findViewById(R.id.AuthUser) as EditText
authUser.setText(acc.authUser)
authPass = findViewById(R.id.AuthPass) as EditText
authPass.setText(acc.authPass)
outbound1 = findViewById(R.id.Outbound1) as EditText
outbound2 = findViewById(R.id.Outbound2) as EditText
if (acc.outbound.size > 0) {
outbound1.setText(acc.outbound[0])
if (acc.outbound.size > 1)
outbound2.setText(acc.outbound[1])
}
regint = findViewById(R.id.RegInt) as EditText
regint.setText(acc.regint.toString())
val audioCodecs = ArrayList(Utils.audio_codecs().split(","))
newCodecs.addAll(audioCodecs)
while (newCodecs.size < audioCodecs.size) newCodecs.add("")
val layout = findViewById(R.id.CodecSpinners) as LinearLayout
for (i in audioCodecs.indices) {
val spinner = Spinner(applicationContext)
spinner.id = i + 100
spinner.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
spinner.setPopupBackgroundResource(R.drawable.spinner_background)
layout.addView(spinner)
var spinnerList = ArrayList<String>()
if (acc.audioCodec.size > i) {
val codec = acc.audioCodec[i]
spinnerList.add(codec)
for (c in audioCodecs) if (c != codec) spinnerList.add(c)
spinnerList.add("")
} else {
spinnerList = audioCodecs
spinnerList.add(0, "")
}
val codecSpinner = findViewById(spinner.id) as Spinner
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_spinner_item,
spinnerList)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
codecSpinner.adapter = adapter
codecSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
newCodecs.set(parent.id - 100, parent.selectedItem.toString())
}
override fun onNothingSelected(parent: AdapterView<*>) {
}
}
}
mediaEnc = acc.mediaenc
val mediaEncSpinner = findViewById(R.id.mediaEncSpinner) as Spinner
val mediaEncKeys = arrayListOf("zrtp", "dtls_srtp", "srtp", "srtp-mand", "")
val mediaEncVals = arrayListOf("ZRTP", "DTLS SRTP", "SRTP", "Madatory SRTP", "")
val keyIx = mediaEncKeys.indexOf(acc.mediaenc)
val keyVal = mediaEncVals.elementAt(keyIx)
mediaEncKeys.removeAt(keyIx)
mediaEncVals.removeAt(keyIx)
mediaEncKeys.add(0, acc.mediaenc)
mediaEncVals.add(0, keyVal)
val mediaEncAdapter = ArrayAdapter<String>(this,android.R.layout.simple_spinner_item,
mediaEncVals)
mediaEncAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
mediaEncSpinner.adapter = mediaEncAdapter
mediaEncSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
mediaEnc = mediaEncKeys[mediaEncVals.indexOf(parent.selectedItem.toString())]
}
override fun onNothingSelected(parent: AdapterView<*>) {
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val intent = Intent(this, MainActivity::class.java)
when (item.itemId) {
R.id.save -> {
val dn = displayName.text.toString().trim()
if (dn != acc.displayName) {
if (checkDisplayName(dn)) {
if (account_set_display_name(acc.accp, dn) == 0) {
acc.displayName = account_display_name(acc.accp);
Log.d("Baresip", "New display name is ${acc.displayName}")
save = true
} else {
Log.e("Baresip", "Setting of display name failed")
}
} else {
Utils.alertView(this, "Notice", "Invalid Display Name: $dn")
return false
}
}
val au = authUser.text.toString().trim()
if (au != acc.authUser) {
if (checkAuthUser(au)) {
if (account_set_auth_user(acc.accp, au) == 0) {
acc.authUser = account_auth_user(acc.accp);
Log.d("Baresip", "New auth user is ${acc.authUser}")
save = true
} else {
Log.e("Baresip", "Setting of auth user failed")
}
} else {
Utils.alertView(this, "Notice",
"Invalid Authentication UserName: $au")
return false
}
}
val ap = authPass.text.toString().trim()
if (ap != acc.authPass) {
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}")
save = true
} else {
Log.e("Baresip", "Setting of auth pass failed")
}
} else {
Utils.alertView(this, "Notice",
"Invalid Authentication Password: $ap")
return false
}
}
val ob = ArrayList<String>()
if (outbound1.text.toString().trim() != "")
ob.add(outbound1.text.toString().trim().replace(" ", ""))
if (outbound2.text.toString().trim() != "")
ob.add(outbound2.text.toString().trim().replace(" ", ""))
if (ob != acc.outbound) {
val outbound = ArrayList<String>()
for (i in ob.indices) {
if ((ob[i] == "") || Utils.uri_decode(ob[i])) {
if (account_set_outbound(acc.accp, ob[i], i) == 0) {
if (ob[i] != "")
outbound.add(account_outbound(acc.accp, i))
} else {
Log.e("Baresip", "Setting of outbound proxy ${ob[i]} failed")
break
}
} else {
Utils.alertView(this, "Notice",
"Invalid Outbound Proxy: ${ob[i]}")
return false
}
}
Log.d("Baresip", "New outbound proxies are ${outbound}")
acc.outbound = outbound
save = true
}
val ri = regint.text.toString().trim()
if (Utils.checkUint(ri)) {
if (ri.toInt() != acc.regint) {
if (account_set_regint(acc.accp, ri.toInt()) == 0) {
acc.regint = account_regint(acc.accp)
Log.d("Baresip", "New regint is ${acc.regint}")
save = true
} else {
Log.e("Baresip", "Setting of regint $ri failed")
}
}
} else {
Utils.alertView(this, "Notice",
"Invalid Registration Interval: $ri")
return false
}
val ac = ArrayList(LinkedHashSet<String>(newCodecs.filter{it != ""} as ArrayList<String>))
if (ac != acc.audioCodec) {
Log.d("Baresip", "New codecs ${newCodecs.filter { it != "" }}")
val acParam = ";audio_codecs=" + Utils.implode(ac, ",")
if (account_set_audio_codecs(acc.accp, acParam) == 0) {
var i = 0
while (true) {
val codec = account_audio_codec(acc.accp, i)
if (codec != "") {
Log.d("Baresip", "Found audio codec $codec")
i++
} else {
break
}
}
acc.audioCodec = ac
save = true
} else {
Log.e("Baresip", "Setting of audio codecs $acParam failed")
}
}
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}")
save = true
} else {
Log.e("Baresip", "Setting of mediaenc $mediaEnc failed")
}
}
if (save) {
AccountsActivity.saveAccounts()
if (acc.regint > 0) {
Log.d("Baresip", "Registering ${acc.aor}")
UserAgent.ua_register(Account.findUA(acc.aor)!!.uap)
}
}
setResult(RESULT_OK, intent)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
intent.putExtra("action", "cancel")
if (acc.regint > 0) {
Log.d("Baresip", "Registering ${acc.aor}")
UserAgent.ua_register(Account.findUA(acc.aor)!!.uap)
}
setResult(RESULT_OK, intent)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
private fun checkDisplayName(dn: String): Boolean {
if (dn == "") return true
val dnRegex = Regex("^[a-zA-Z]([ ._-]|[a-zA-Z0-9]){1,63}\$")
return dnRegex.matches(dn)
}
private fun checkAuthUser(au: String): Boolean {
if (au == "") return true
val ud = au.split("@")
val userIDRegex = Regex("^[a-zA-Z]([._-]|[a-zA-Z0-9]){1,63}\$")
val telnoRegex = Regex("^[+]?[0-9]{1,16}\$")
if (ud.size == 1) {
return userIDRegex.matches(ud[0]) || telnoRegex.matches(ud[0])
} else {
return (userIDRegex.matches(ud[0]) || telnoRegex.matches(ud[0])) &&
Utils.checkDomain(ud[1])
}
}
}

View File

@ -0,0 +1,61 @@
package com.tutpro.baresip
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageButton
import android.widget.TextView
import java.util.*
class AccountListAdapter(private val cxt: Context, private val rows: ArrayList<AccountRow>) :
ArrayAdapter<AccountRow>(cxt, R.layout.account_row, rows) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val row = rows[position]
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rowView = inflater.inflate(R.layout.account_row, parent, false)
val aorView = rowView.findViewById(R.id.aor) as TextView
aorView.text = row.aor
aorView.textSize = 20f
aorView.setPadding(6, 6, 0, 6)
aorView.setOnClickListener { _ ->
val i = Intent(cxt, AccountActivity::class.java)
val b = Bundle()
b.putString("accp", MainActivity.uas[position].account.accp)
i.putExtras(b)
cxt.startActivity(i)
}
val actionView = rowView.findViewById(R.id.action) as ImageButton
actionView.setImageResource(row.action)
actionView.setOnClickListener { _ ->
Log.d("Baresip", "Delete button clicked")
val deleteDialog = AlertDialog.Builder(cxt)
deleteDialog.setMessage("Do you want to delete account ${MainActivity.uas[position].aor}?")
deleteDialog.setPositiveButton("Delete") { dialog, _ ->
if (UserAgent.ua_isregistered(MainActivity.uas[position].uap)) {
UserAgent.ua_unregister(MainActivity.uas[position].uap)
}
UserAgent.ua_destroy(MainActivity.uas[position].uap)
MainActivity.uas.remove(MainActivity.uas[position])
MainActivity.images.removeAt(position)
AccountsActivity.saveAccounts()
AccountsActivity.generateAccounts()
this.notifyDataSetChanged()
dialog.dismiss()
}
deleteDialog.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}
deleteDialog.create().show()
}
return rowView
}
}

View File

@ -0,0 +1,3 @@
package com.tutpro.baresip
class AccountRow(val aor: String, val action: Int)

View File

@ -0,0 +1,100 @@
package com.tutpro.baresip
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.MenuItem
import android.widget.*
import java.io.File
import java.util.ArrayList
class AccountsActivity : AppCompatActivity() {
internal var aor: String = ""
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_accounts)
val listview = findViewById(R.id.accounts) as ListView
generateAccounts()
val alAdapter = AccountListAdapter(this, accounts)
listview.adapter = alAdapter
val addAccountButton = findViewById(R.id.addAccount) as ImageButton
val newAorView = findViewById(R.id.newAor) as EditText
addAccountButton.setOnClickListener{
var aor = newAorView.text.toString()
if (!aor.startsWith("sip:")) aor = "sip:$aor"
if (!checkSipUri(aor)) {
Log.e("Baresip", "Invalid SIP URI $aor")
Utils.alertView(this, "Notice",
"Invalid SIP URI: $aor")
} else if (Account.exists(MainActivity.uas, aor)) {
Log.e("Baresip", "Account $aor already exists")
} else {
if (UserAgent.ua_alloc("<$aor>;regq=0.5;pubint=0;regint=600") != 0)
Log.e("Baresip", "Failed to allocate UA $aor")
else {
SystemClock.sleep(200);
newAorView.setText("")
newAorView.setHint("SIP URI user@domain")
newAorView.clearFocus()
saveAccounts()
recreate()
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
Log.d("Baresip", "Back array was pressed at Accounts")
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
}
return true
}
private fun checkSipUri(uri: String): Boolean {
if (!uri.startsWith("sip:")) return false
val userDomain = uri.replace("sip:", "").split("@")
if (userDomain.size != 2) return false
if (!Utils.checkUserID(userDomain[0]) && !Utils.checkTelNo(userDomain[0]))
return false
return Utils.checkDomain(userDomain[1]) || Utils.checkIP(userDomain[1])
}
companion object {
var accounts = ArrayList<AccountRow>()
var posAtAccounts = ArrayList<Int>()
fun generateAccounts() {
accounts.clear()
posAtAccounts.clear()
var i = 0;
for (ua in MainActivity.uas) {
accounts.add(AccountRow(ua.aor.replace("sip:", ""), R.drawable.action_remove))
posAtAccounts.add(i)
i++
}
}
fun saveAccounts() {
var accounts = ""
for (a in Account.accounts()) accounts = accounts + a.print() + "\n"
val path = MainActivity.filesPath + "/accounts"
Utils.putFileContents(File(path), accounts)
Log.d("Baresip", "Saved accounts '${accounts}' to '$path")
}
}
}

View File

@ -3,31 +3,47 @@ package com.tutpro.baresip
import android.text.TextWatcher
import java.util.ArrayList
class Call(val ua: String, val call: String, val peerURI: String, var status: String,
val dtmfWatcher: TextWatcher?) {
class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: String,
var status: String, val dtmfWatcher: TextWatcher?) {
var hold: Boolean = false
var security: Int = 0
var zid: String = ""
var hold = false
var security = 0
var zid = ""
var hasHistory = false
companion object {
fun uaCalls(calls: ArrayList<Call>, ua: String): ArrayList<Call> {
fun calls(calls: ArrayList<Call>, dir: String): ArrayList<Call> {
val result = ArrayList<Call>()
for (i in calls.indices) {
if (calls[i].ua == ua) result.add(calls[i])
if (calls[i].dir == dir) result.add(calls[i])
}
return result
}
fun callIndex(calls: ArrayList<Call>, ua: String, call: String): Int {
fun uaCalls(calls: ArrayList<Call>, ua: UserAgent, dir: String): ArrayList<Call> {
val result = ArrayList<Call>()
for (i in calls.indices) {
if (calls[i].ua.equals(ua) && calls[i].call == call)
return i
if ((calls[i].ua == ua) && (calls[i].dir == dir)) result.add(calls[i])
}
return -1
return result
}
fun find(calls: ArrayList<Call>, callp: String): Call? {
for (c in calls) {
if (c.callp == callp) return c
}
return null
}
fun index(calls: ArrayList<Call>, call: Call, dir: String): Int {
var res = 0
for (c in calls) {
if (c == call) return res
if ((dir == "") || (call.dir == dir)) res++
}
return res
}
}
}

View File

@ -0,0 +1,3 @@
package com.tutpro.baresip
class Codec(val name: String, val rate: Int, val channels: Int) {}

View File

@ -1,103 +0,0 @@
package com.tutpro.baresip
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.MenuInflater
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 EditAccountsActivity : 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_accounts)
editText = findViewById(R.id.editAccounts) as EditText
path = applicationContext.filesDir.absolutePath + "/accounts"
file = File(path!!)
var content: String
if (!file!!.exists()) {
Log.e("Baresip", "Failed to find accounts file")
content = "No accounts"
} else {
Log.e("Baresip", "Found accounts 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 accounts 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.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent()
when (item.itemId) {
R.id.save -> {
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 accounts file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find accounts file: " + e.toString())
}
Log.d("Baresip", "Updated accounts file")
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
setResult(RESULT_CANCELED, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
}

View File

@ -3,8 +3,8 @@ package com.tutpro.baresip
import java.io.Serializable
import java.util.GregorianCalendar
class History(val ua: String, val call: String, val aor: String, val peerURI: String,
val direction: String, val connected: Boolean) : Serializable {
class History(val aor: String, val peerURI: String, val direction: String,
val connected: Boolean) : Serializable {
val time: GregorianCalendar = GregorianCalendar()

View File

@ -30,7 +30,7 @@ class HistoryActivity : AppCompatActivity() {
val b = intent.extras
aor = b.getString("aor")
generate_ua_history(aor)
aorGenerateHistory(aor)
val adapter = HistoryListAdapter(this, uaHistory)
listview.adapter = adapter
@ -46,7 +46,7 @@ class HistoryActivity : AppCompatActivity() {
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
History.removeAt(posAtHistory[pos])
generate_ua_history(aor)
aorGenerateHistory(aor)
if (uaHistory.size == 0) {
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
@ -82,7 +82,7 @@ class HistoryActivity : AppCompatActivity() {
return true
}
private fun generate_ua_history(aor: String) {
private fun aorGenerateHistory(aor: String) {
uaHistory.clear()
posAtHistory.clear()
for (i in History.indices.reversed()) {
@ -137,13 +137,6 @@ class HistoryActivity : AppCompatActivity() {
return size
}
fun callHasHistory(ua: String, call: String): Boolean {
for (h in History) {
if (h.ua == ua && h.call == call) return true
}
return false
}
fun aorRemoveHistory(aor: String) {
for (h in History) {
if (h.aor == aor) {

View File

@ -1,7 +1,6 @@
package com.tutpro.baresip
import android.Manifest
import android.app.Notification
import android.app.Notification.VISIBILITY_PUBLIC
import android.app.NotificationManager
import android.app.PendingIntent
@ -35,14 +34,14 @@ import java.io.*
class MainActivity : AppCompatActivity() {
internal lateinit var appContext: Context
lateinit var appContext: Context
internal lateinit var layout: RelativeLayout
internal lateinit var callee: AutoCompleteTextView
internal lateinit var securityButton: ImageButton
internal lateinit var callButton: Button
internal lateinit var holdButton: Button
internal lateinit var dtmf: EditText
internal lateinit var accountAdapter: AccountSpinnerAdapter
internal lateinit var uaAdapter: UaSpinnerAdapter
internal lateinit var aorSpinner: Spinner
internal lateinit var am: AudioManager
internal lateinit var imm: InputMethodManager
@ -61,6 +60,7 @@ class MainActivity : AppCompatActivity() {
callButton = findViewById(R.id.callButton) as Button
holdButton = findViewById(R.id.holdButton) as Button
dtmf = findViewById(R.id.dtmf) as EditText
filesPath = applicationContext.filesDir.absolutePath
am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -78,15 +78,16 @@ class MainActivity : AppCompatActivity() {
.setContent(RemoteViews(getPackageName(), R.layout.notification))
aorSpinner = findViewById(R.id.AoRList) as Spinner
accountAdapter = AccountSpinnerAdapter(applicationContext, accounts, images)
aorSpinner.adapter = accountAdapter
uaAdapter = UaSpinnerAdapter(applicationContext, uas, images)
aorSpinner.adapter = uaAdapter
aorSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val aor = accounts[position].aoR
val ua = accounts[position].ua
val acc = uas[position].account
val aor = acc.aor
val ua = uas[position]
Log.i("Baresip", "Setting $aor current")
val out = Call.uaCalls(callsOut, ua)
if (out.size == 0) {
val callsOut = Call.uaCalls(calls, ua, "out")
if (callsOut.size == 0) {
callee.text.clear()
callee.hint = "Callee"
callButton.text = "Call"
@ -99,18 +100,18 @@ class MainActivity : AppCompatActivity() {
securityButton.visibility = View.INVISIBLE
dtmf.visibility = View.INVISIBLE
} else {
callee.setText(out[0].peerURI)
callButton.text = out[0].status
if (out[0].status == "Hangup") {
if (out[0].hold) {
callee.setText(callsOut[0].peerURI)
callButton.text = callsOut[0].status
if (callsOut[0].status == "Hangup") {
if (callsOut[0].hold) {
holdButton.text = "Unhold"
} else {
holdButton.text = "Hold"
}
holdButton.visibility = View.VISIBLE
securityButton.setImageResource(out[0].security)
Utils.setSecurityButtonTag(securityButton, out[0].security)
if (ua_mediaenc(ua) == "")
securityButton.setImageResource(callsOut[0].security)
setSecurityButtonTag(securityButton, callsOut[0].security)
if (acc.mediaenc == "")
securityButton.visibility = View.INVISIBLE
else
securityButton.visibility = View.VISIBLE
@ -122,7 +123,7 @@ class MainActivity : AppCompatActivity() {
dtmf.visibility = View.INVISIBLE
}
}
val `in` = Call.uaCalls(callsIn, ua)
val `in` = Call.uaCalls(calls, ua, "in")
val view_count = layout.childCount
Log.d("Baresip", "View count is $view_count")
if (view_count > 7) {
@ -130,7 +131,7 @@ class MainActivity : AppCompatActivity() {
layout.removeViews(7, view_count - 7)
}
for (c in `in`) {
for (call_index in callsIn.indices) {
for (call_index in Call.calls(calls, "in").indices) {
addCallViews(ua, c, (call_index + 1) * 10)
}
}
@ -141,6 +142,23 @@ class MainActivity : AppCompatActivity() {
}
}
aorSpinner.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if ((event.x - view.left) < 100) {
val i = Intent(this, AccountActivity::class.java)
val b = Bundle()
b.putString("accp", uas[aorSpinner.selectedItemPosition].account.accp)
i.putExtras(b)
startActivityForResult(i, ACCOUNT_CODE)
true
} else {
false
}
} else {
false
}
}
val assets = arrayOf("accounts", "contacts", "config", "busy.wav", "callwaiting.wav",
"error.wav", "message.wav", "notfound.wav", "ring.wav", "ringback.wav")
val path = applicationContext.filesDir.path
@ -159,7 +177,7 @@ class MainActivity : AppCompatActivity() {
file = File("$path/$a")
if (!file.exists()) {
Log.d("Baresip", "Copying asset $a")
Utils.copyAssetToFile(applicationContext, a, "$path/$a")
copyAssetToFile(applicationContext, a, "$path/$a")
} else {
Log.d("Baresip", "Asset $a already copied")
}
@ -207,11 +225,11 @@ class MainActivity : AppCompatActivity() {
unverifyDialog.setMessage("This call is SECURE and peer is VERIFIED! " +
"Do you want to unverify the peer?")
unverifyDialog.setPositiveButton("Unverify") { dialog, _ ->
val out = Call.uaCalls(callsOut, accounts[aorSpinner.selectedItemPosition].ua)
if (out.size > 0) {
if (cmd_exec("zrtp_unverify " + out[0].zid) != 0) {
val outCalls = Call.uaCalls(calls, uas[aorSpinner.selectedItemPosition], "out")
if (outCalls.size > 0) {
if (Utils.cmd_exec("zrtp_unverify " + outCalls[0].zid) != 0) {
Log.w("Baresip",
"Command 'zrtp_unverify ${out[0].zid}' failed")
"Command 'zrtp_unverify ${outCalls[0].zid}' failed")
} else {
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
@ -229,8 +247,8 @@ class MainActivity : AppCompatActivity() {
callButton.text = "Call"
callButton.setOnClickListener {
val aor = accounts[aorSpinner.selectedItemPosition].aoR
val ua = accounts[aorSpinner.selectedItemPosition].ua
val ua = uas[aorSpinner.selectedItemPosition]
val aor = ua.aor
when (callButton.text.toString()) {
"Call" -> {
val callee = (findViewById(R.id.callee) as EditText).text.toString()
@ -245,14 +263,16 @@ class MainActivity : AppCompatActivity() {
}
}
"Cancel" -> {
Log.i("Baresip", "Canceling AoR $aor call " +
callsOut[0].call + " to " + (findViewById(R.id.callee) as EditText).text)
ua_hangup(ua, callsOut[0].call, 486, "Rejected")
val callp = Call.calls(calls, "out")[0].callp
Log.i("Baresip", "Canceling AoR $aor call $callp to " +
(findViewById(R.id.callee) as EditText).text)
ua_hangup(ua.uap, callp, 486, "Rejected")
}
"Hangup" -> {
Log.i("Baresip", "Hanging up AoR $aor call " +
callsOut[0].call + " to " + (findViewById(R.id.callee) as EditText).text)
ua_hangup(ua, callsOut[0].call,0, "")
val callp = Call.calls(calls, "out")[0].callp
Log.i("Baresip", "Hanging up AoR $aor call $callp to " +
(findViewById(R.id.callee) as EditText).text)
ua_hangup(ua.uap, callp,0, "")
}
}
}
@ -262,20 +282,22 @@ class MainActivity : AppCompatActivity() {
when (holdButton.text.toString()) {
"Hold" -> {
Log.i("Baresip", "Holding up " + (findViewById(R.id.callee) as EditText).text)
call_hold(callsOut[0].call)
callsOut[0].hold = true
val call = Call.calls(calls, "out")[0]
call_hold(call.callp)
call.hold = true
holdButton.text = "Unhold"
}
"Unhold" -> {
Log.i("Baresip", "Unholding " + (findViewById(R.id.callee) as EditText).text)
call_unhold(callsOut[0].call)
callsOut[0].hold = false
val call = Call.calls(calls, "out")[0]
call_unhold(call.callp)
call.hold = false
holdButton.text = "Hold"
}
"History" -> {
val i = Intent(this@MainActivity, HistoryActivity::class.java)
val b = Bundle()
b.putString("aor", accounts[aorSpinner.selectedItemPosition].aoR)
b.putString("aor", uas[aorSpinner.selectedItemPosition].aor)
i.putExtras(b)
startActivityForResult(i, HISTORY_CODE)
}
@ -337,8 +359,8 @@ class MainActivity : AppCompatActivity() {
return true
}
R.id.accounts -> {
i = Intent(this, EditAccountsActivity::class.java)
startActivityForResult(i, EDIT_ACCOUNTS_CODE)
i = Intent(this, AccountsActivity::class.java)
startActivityForResult(i, ACCOUNTS_CODE)
return true
}
R.id.contacts -> {
@ -372,7 +394,6 @@ class MainActivity : AppCompatActivity() {
Log.w("Baresip", "OutputStream exception: " + e.toString())
e.printStackTrace()
}
baresipStop()
nm.cancel(10)
running = false
@ -389,14 +410,11 @@ class MainActivity : AppCompatActivity() {
when (requestCode) {
EDIT_ACCOUNTS_CODE -> {
if (resultCode == RESULT_OK) {
Utils.alertView(this, "Notice",
"You need to restart baresip in order to activate saved accounts!")
}
if (resultCode == RESULT_CANCELED) {
ACCOUNTS_CODE -> {
if (resultCode == RESULT_OK)
Log.d("Baresip", "Edit accounts OK")
if (resultCode == RESULT_CANCELED)
Log.d("Baresip", "Edit accounts canceled")
}
}
EDIT_CONFIG_CODE -> {
@ -418,7 +436,7 @@ class MainActivity : AppCompatActivity() {
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "History canceled")
if (HistoryActivity.aorHistory(accounts[aorSpinner.selectedItemPosition].aoR) == 0) {
if (HistoryActivity.aorHistory(uas[aorSpinner.selectedItemPosition].aor) == 0) {
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
@ -430,12 +448,12 @@ class MainActivity : AppCompatActivity() {
}
}
private fun call(ua: String, uri: String) {
private fun call(ua: UserAgent, uri: String) {
(findViewById(R.id.callee) as EditText).setText(uri)
Log.i("Baresip", "Calling $ua / $uri")
val call = ua_connect(ua, uri)
Log.i("Baresip", "Calling $ua.uap / $uri")
val call = ua_connect(ua.uap, uri)
if (call != "") {
Log.i("Baresip", "Adding outgoing call $ua / $call / $uri")
Log.i("Baresip", "Adding outgoing call $ua.uap / $call / $uri")
val dtmfWatcher = object: TextWatcher {
override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) {
@ -447,15 +465,16 @@ class MainActivity : AppCompatActivity() {
call_send_digit(call, 4.toChar())
}
}
callsOut.add(Call(ua, call, uri, "Cancel", dtmfWatcher))
calls.add(Call(call, ua, uri, "out","Cancel", dtmfWatcher))
(findViewById(R.id.callButton) as Button).text = "Cancel"
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
private fun addCallViews(ua: String, call: Call, id: Int) {
private fun addCallViews(ua: UserAgent, call: Call, id: Int) {
Log.d("Baresip", "Creating new Incoming textview at $id")
val acc = ua.account
val caller_heading = TextView(appContext)
caller_heading.text = "Incoming call from ..."
caller_heading.setTextColor(Color.BLACK)
@ -480,7 +499,7 @@ class MainActivity : AppCompatActivity() {
caller_row.layoutParams = caller_row_params
val caller_uri = TextView(appContext)
caller_uri.text = callsIn[callsIn.size - 1].peerURI
caller_uri.text = call.peerURI
caller_uri.setTextColor(Color.BLUE)
caller_uri.textSize = 20f
caller_uri.setPadding(10, 10, 0, 10)
@ -497,14 +516,42 @@ class MainActivity : AppCompatActivity() {
val security_button_params = LinearLayout.LayoutParams(dp24px, dp24px, 0.0f)
security_button_params.gravity = Gravity.CENTER_VERTICAL
security_button.layoutParams = security_button_params
if ((call.security != 0) && (ua_mediaenc(ua) != "")) {
if ((call.security != 0) && (acc.mediaenc != "")) {
security_button.setImageResource(call.security)
Utils.setSecurityButtonTag(securityButton, call.security)
setSecurityButtonTag(securityButton, call.security)
security_button.visibility = View.VISIBLE
} else {
security_button.visibility = View.INVISIBLE
}
Utils.setSecurityButtonOnClickListener(this, security_button, call)
security_button.setOnClickListener {
when (security_button.tag) {
"red" -> {
Utils.alertView(this, "Alert", "This call is NOT secure!")
}
"yellow" -> {
Utils.alertView(this, "Alert",
"This call is SECURE, but peer is NOT verified!")
}
"green" -> {
val unverifyDialog = AlertDialog.Builder(this)
unverifyDialog.setMessage("This call is SECURE and peer is VERIFIED! " +
"Do you want to unverify the peer?")
unverifyDialog.setPositiveButton("Unverify") { dialog, _ ->
if (Utils.cmd_exec("zrtp_unverify " + call.zid) != 0) {
Log.w("Baresip", "Command 'zrtp_unverify ${call.zid}' failed")
} else {
security_button.setImageResource(R.drawable.box_yellow)
security_button.tag = "yellow"
}
dialog.dismiss()
}
unverifyDialog.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}
unverifyDialog.create().show()
}
}
}
caller_row.addView(security_button)
layout.addView(caller_row)
@ -516,20 +563,16 @@ class MainActivity : AppCompatActivity() {
Log.d("Baresip", "Creating new answer button at " + (id + 4))
answer_button.id = id + 4
answer_button.setOnClickListener { v ->
val call_ua = call.ua
val call_call = call.call
when ((v as Button).text.toString()) {
"Answer" -> {
Log.i("Baresip", "UA " + call_ua + " accepting incoming call " +
call_call)
ua_answer(call_ua, call_call)
val final_in_index = Call.callIndex(callsIn, call_ua, call_call)
if (final_in_index >= 0) {
Log.i("Baresip", "UA ${call.ua.uap} accepting incoming call ${call.callp}")
ua_answer(call.ua.uap, call.callp)
if (call.dir == "in") {
Log.d("Baresip", "Updating Hangup and Hold")
callsIn[final_in_index].status = "Hangup"
callsIn[final_in_index].hold = false
call.status = "Hangup"
call.hold = false
this@MainActivity.runOnUiThread {
val answer_id = (final_in_index + 1) * 10 + 4
val answer_id = (Call.index(calls, call,"in") + 1) * 10 + 4
val answer_but = layout.findViewById(answer_id) as Button
answer_but.text = "Hangup"
val reject_button = layout.findViewById(answer_id + 1) as Button
@ -538,9 +581,8 @@ class MainActivity : AppCompatActivity() {
}
}
"Hangup" -> {
Log.i("Baresip", "UA " + call_ua + " hanging up call " +
call_call)
ua_hangup(call_ua, call_call, 200, "OK")
Log.i("Baresip", "UA ${call.ua.uap} hanging up call ${call.callp}")
ua_hangup(call.ua.uap, call.callp, 200, "OK")
}
else -> Log.e("Baresip", "Invalid answer button text: " + v.text.toString())
}
@ -569,16 +611,16 @@ class MainActivity : AppCompatActivity() {
when ((v as Button).text.toString()) {
"Reject" -> {
Log.i("Baresip", "UA " + call.ua +
" rejecting incoming call " + call.call)
ua_hangup(call.ua, call.call, 486, "Rejected")
" rejecting incoming call " + call.callp)
ua_hangup(call.ua.uap, call.callp, 486, "Rejected")
}
"Hold" -> {
call_hold(call.call)
call_hold(call.callp)
v.text = "Unhold"
call.hold = true
}
"Unhold" -> {
call_unhold(call.call)
call_unhold(call.callp)
v.text = "Hold"
call.hold = false
}
@ -596,14 +638,14 @@ class MainActivity : AppCompatActivity() {
val contentView = RemoteViews(getPackageName(), R.layout.notification)
for (i: Int in 0 .. 3) {
val resID = resources.getIdentifier("status$i", "id", packageName);
if (i < accounts.size) {
if (i < images.size) {
contentView.setImageViewResource(resID, images[i])
contentView.setViewVisibility(resID, View.VISIBLE)
} else {
contentView.setViewVisibility(resID, View.INVISIBLE)
}
}
if (accounts.size > 4)
if (images.size > 4)
contentView.setViewVisibility(R.id.etc, View.VISIBLE)
else
contentView.setViewVisibility(R.id.etc, View.INVISIBLE)
@ -613,63 +655,70 @@ class MainActivity : AppCompatActivity() {
@Keep
fun addAccount(ua: String) {
val aor = ua_aor(ua)
Log.d("Baresip", "Adding account $ua with AoR $aor")
accounts.add(Account(ua, aor, ua_mediaenc(ua),""))
val userAgent = UserAgent(ua)
uas.add(userAgent)
images.add(R.drawable.dot_yellow)
this@MainActivity.runOnUiThread {
accountAdapter.notifyDataSetChanged()
uaAdapter.notifyDataSetChanged()
updateNotification()
}
}
@Keep
private fun updateStatus(event: String, ua: String, call: String) {
private fun updateStatus(event: String, uap: String, callp: String) {
val aor = ua_aor(ua)
val ua = UserAgent.find(uas, uap)
if (ua == null) {
Log.e("Baresip", "Update status did not find ua $uap")
return
}
val aor = ua.aor
val acc = ua.account
val ev = event.split(",")
Log.d("Baresip", "Handling event ${ev[0]} for $ua/$call/$aor")
Log.d("Baresip", "Handling event ${ev[0]} for $uap/$callp/$aor")
for (account_index in accounts.indices) {
if (accounts[account_index].aoR == aor) {
for (account_index in uas.indices) {
if (uas[account_index].aor == aor) {
Log.d("Baresip", "Found AoR at index $account_index")
when (ev[0]) {
"registering", "unregistering" -> {
}
"registered" -> {
Log.d("Baresip", "Setting status to green")
accounts[account_index].status = "OK"
images[account_index] = R.drawable.dot_green
this@MainActivity.runOnUiThread {
accountAdapter.notifyDataSetChanged()
uaAdapter.notifyDataSetChanged()
updateNotification()
}
}
"registering failed" -> {
Log.d("Baresip", "Setting status to red")
accounts[account_index].status = "FAIL"
images[account_index] = R.drawable.dot_red
this@MainActivity.runOnUiThread {
accountAdapter.notifyDataSetChanged()
uaAdapter.notifyDataSetChanged()
updateNotification()
}
}
"call ringing" -> {
}
"call established" -> {
val out_index = Call.callIndex(callsOut, ua, call)
if (out_index != -1) {
Log.d("Baresip", "Outbound call $call established")
callsOut[out_index].status = "Hangup"
callsOut[out_index].hold = false
callsOut[out_index].security = R.drawable.box_red
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Established call $callp not found")
return
}
if (call.dir == "out") {
Log.d("Baresip", "Outbound call $callp established")
call.status = "Hangup"
call.hold = false
call.security = R.drawable.box_red
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
if (ua == uas[aorSpinner.selectedItemPosition]) {
callButton.text = "Hangup"
holdButton.text = "Hold"
holdButton.visibility = View.VISIBLE
if (ua_mediaenc(ua) == "") {
if (acc.mediaenc == "") {
securityButton.visibility = View.INVISIBLE
} else {
securityButton.setImageResource(R.drawable.box_red)
@ -680,36 +729,34 @@ class MainActivity : AppCompatActivity() {
dtmf.hint = "DTMF"
dtmf.visibility = View.VISIBLE
dtmf.requestFocus()
dtmf.addTextChangedListener(callsOut[out_index].dtmfWatcher)
dtmf.addTextChangedListener(call.dtmfWatcher)
}
}
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"out", true))
HistoryActivity.History.add(History(aor, call.peerURI, "out",
true))
call.hasHistory = true
} else {
val in_index = Call.callIndex(callsIn, ua, call)
if (in_index != -1) {
Log.d("Baresip", "Inbound call $call established")
callsIn[in_index].status = "Answer"
callsIn[in_index].security = R.drawable.box_red
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
if (ua_mediaenc(ua) != "") {
var view_id = (in_index + 1) * 10 + 3
val securityButton = findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_red)
securityButton.tag = "red"
securityButton.visibility = View.VISIBLE
view_id = (in_index + 1) * 10 + 5
val rejectButton = findViewById(view_id) as Button
rejectButton.text = "Hold"
}
Log.d("Baresip", "Inbound call $callp established")
call.status = "Answer"
call.security = R.drawable.box_red
this@MainActivity.runOnUiThread {
if (ua == uas[aorSpinner.selectedItemPosition]) {
if (acc.mediaenc != "") {
val inIndex = Call.index(calls, call, "in")
var view_id = (inIndex + 1) * 10 + 3
val securityButton = findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_red)
securityButton.tag = "red"
securityButton.visibility = View.VISIBLE
view_id = (inIndex + 1) * 10 + 5
val rejectButton = findViewById(view_id) as Button
rejectButton.text = "Hold"
}
}
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"in", true))
} else {
Log.e("Baresip", "Unknown call $ua/$call established")
}
HistoryActivity.History.add(History(aor, call.peerURI, "in",
true))
call.hasHistory = true
}
am.mode = AudioManager.MODE_IN_COMMUNICATION
am.isSpeakerphoneOn = false
@ -720,77 +767,67 @@ class MainActivity : AppCompatActivity() {
HistoryActivity.aorRemoveHistory(aor)
}
"call incoming" -> {
val peer_uri = call_peeruri(call)
Log.d("Baresip", "Incoming call " + ua + "/" + call + "/" +
peer_uri)
val new_call = Call(ua, call, peer_uri, "Answer", null)
callsIn.add(new_call)
val peer_uri = call_peeruri(callp)
Log.d("Baresip", "Incoming call $uap/$callp/$peer_uri")
val new_call = Call(callp, ua, peer_uri, "in", "Answer", null)
calls.add(new_call)
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
addCallViews(ua, new_call, callsIn.size * 10)
if (ua == uas[aorSpinner.selectedItemPosition]) {
addCallViews(ua, new_call, Call.calls(calls, "in").size * 10)
}
}
am.mode = AudioManager.MODE_RINGTONE
// am.isSpeakerphoneOn = true
}
"call verify" -> {
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp to be verified is not found")
return
}
this@MainActivity.runOnUiThread {
val verifyDialog = AlertDialog.Builder(this)
verifyDialog.setTitle("Verify")
verifyDialog.setMessage("Do you want to verify SAS <${ev[1]}> <${ev[2]}>?")
verifyDialog.setPositiveButton("Yes") { dialog, _ ->
val security: Int
if (cmd_exec("zrtp_verify ${ev[3]}") != 0) {
if (Utils.cmd_exec("zrtp_verify ${ev[3]}") != 0) {
Log.w("Baresip", "Command 'zrtp_verify ${ev[3]}' failed")
security = R.drawable.box_yellow
} else {
security = R.drawable.box_green
}
var index = Call.callIndex(callsOut, ua, call)
if (index != -1) {
callsOut[index].security = security
callsOut[index].zid = ev[3]
call.security = security
call.zid = ev[3]
if (call.dir == "out") {
securityButton.setImageResource(security)
Utils.setSecurityButtonTag(securityButton, security)
setSecurityButtonTag(securityButton, security)
securityButton.visibility = View.VISIBLE
} else {
index = Call.callIndex(callsIn, ua, call)
if (index != -1) {
callsIn[index].security = security
callsIn[index].zid = ev[3]
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
val view_id = (index + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(security)
Utils.setSecurityButtonTag(securityButton, security)
securityButton.visibility = View.VISIBLE
}
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.index(calls, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(security)
setSecurityButtonTag(securityButton, security)
securityButton.visibility = View.VISIBLE
}
}
dialog.dismiss()
}
verifyDialog.setNegativeButton("No") { dialog, _ ->
var index = Call.callIndex(callsOut, ua, call)
if (index >= 0) {
callsOut[index].security = R.drawable.box_yellow
callsOut[index].zid = ev[3]
call.security = R.drawable.box_yellow
call.zid = ev[3]
if (call.dir == "out") {
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
securityButton.visibility = View.VISIBLE
} else {
index = Call.callIndex(callsIn, ua, call)
if (index != -1) {
callsIn[index].security = R.drawable.box_yellow
callsIn[index].zid = ev[3]
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
val view_id = (index + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
securityButton.visibility = View.VISIBLE
}
} else {
Log.e("Baresip", "Unknown call $ua/$call to verify")
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.index(calls, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
securityButton.visibility = View.VISIBLE
}
}
dialog.dismiss()
@ -799,46 +836,48 @@ class MainActivity : AppCompatActivity() {
}
}
"call verified" -> {
val out_index = Call.callIndex(callsOut, ua, call)
if (out_index != -1) {
callsOut[out_index].security = R.drawable.box_green
callsOut[out_index].zid = ev[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp that is verified is not found")
return
}
call.security = R.drawable.box_green
call.zid = ev[1]
if (call.dir == "out") {
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
if (ua == uas[aorSpinner.selectedItemPosition]) {
securityButton.setImageResource(R.drawable.box_green)
securityButton.tag = "green"
securityButton.visibility = View.VISIBLE
}
}
} else {
val in_index = Call.callIndex(callsIn, ua, call)
if (in_index != -1) {
callsIn[in_index].security = R.drawable.box_green
callsIn[in_index].zid = ev[1]
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
val view_id = (in_index + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_green)
securityButton.tag = "green"
securityButton.visibility = View.VISIBLE
}
this@MainActivity.runOnUiThread {
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.index(calls, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_green)
securityButton.tag = "green"
securityButton.visibility = View.VISIBLE
}
} else {
Log.e("Baresip", "Unknown call $ua/$call verified")
}
}
}
"call closed" -> {
val in_index = Call.callIndex(callsIn, ua, call)
if (in_index != -1) {
Log.d("Baresip", "Removing inbound call " + ua + "/" +
call + "/" + callsIn[in_index].peerURI)
val view_id = (in_index + 1) * 10
val remove_count = callsIn.size - in_index
callsIn.removeAt(in_index)
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp that is closed is not found")
return
}
if (call.dir == "in") {
Log.d("Baresip", "Removing inbound call $uap / $callp / " +
call.peerURI)
val inIndex = Call.index(calls, call, "in")
val view_id = (inIndex + 1) * 10
val remove_count = Call.calls(calls, "in").size - inIndex
calls.removeAt(Call.index(calls, call, ""))
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
if (ua == uas[aorSpinner.selectedItemPosition]) {
if (callButton.text == "Call") {
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
@ -847,24 +886,23 @@ class MainActivity : AppCompatActivity() {
val caller_heading = layout.findViewById(view_id)
val view_index = layout.indexOfChild(caller_heading)
layout.removeViews(view_index, 4 * remove_count)
for (i in in_index until callsIn.size) {
val callsIn = Call.calls(calls, "in")
for (i in inIndex until callsIn.size) {
this@MainActivity.addCallViews(ua, callsIn[i], (i + 1) * 10)
}
}
}
if (!HistoryActivity.callHasHistory(ua, call)) {
if (!call.hasHistory) {
if (HistoryActivity.aorHistory(aor) > HISTORY_SIZE)
HistoryActivity.aorRemoveHistory(aor)
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
HistoryActivity.History.add(History(aor, call.peerURI,
"in", false))
}
} else {
val out_index = Call.callIndex(callsOut, ua, call)
if (out_index != -1) {
Log.d("Baresip", "Removing outgoing call " + ua + "/" +
call + "/" + callsOut[out_index].peerURI)
Log.d("Baresip", "Removing outgoing call $uap / $callp / " +
call.peerURI)
this@MainActivity.runOnUiThread {
if (ua == accounts[aorSpinner.selectedItemPosition].ua) {
if (ua == uas[aorSpinner.selectedItemPosition]) {
callButton.text = "Call"
callButton.isEnabled = true
callee.setText("")
@ -875,24 +913,19 @@ class MainActivity : AppCompatActivity() {
imm.hideSoftInputFromWindow(dtmf.getWindowToken(), 0)
}
securityButton.visibility = View.INVISIBLE
dtmf.removeTextChangedListener(callsOut[out_index].dtmfWatcher)
dtmf.removeTextChangedListener(call.dtmfWatcher)
dtmf.visibility = View.INVISIBLE
}
callsOut.removeAt(out_index)
calls.removeAt(Call.index(calls, call, ""))
}
if (!HistoryActivity.callHasHistory(ua, call)) {
if (!call.hasHistory) {
if (HistoryActivity.aorHistory(aor) > HISTORY_SIZE)
HistoryActivity.aorRemoveHistory(aor)
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
HistoryActivity.History.add(History(aor, call.peerURI,
"out", false))
}
} else {
Log.e("Baresip", "Unknown call " + ua + "/" + call +
" closed")
}
}
if ((callsIn.size == 0) && (callsOut.size == 0))
am.mode = AudioManager.MODE_NORMAL
if (calls.size == 0) am.mode = AudioManager.MODE_NORMAL
}
else -> Log.d("Baresip", "Unknown event '${ev[0]}'")
}
@ -900,33 +933,56 @@ class MainActivity : AppCompatActivity() {
}
}
private fun copyAssetToFile(context: Context, asset: String, path: String) {
try {
val `is` = context.assets.open(asset)
val os = FileOutputStream(path)
val buffer = ByteArray(512)
var byteRead: Int = `is`.read(buffer)
while (byteRead != -1) {
os.write(buffer, 0, byteRead)
byteRead = `is`.read(buffer)
}
} catch (e: IOException) {
Log.e("Baresip", "Failed to read asset " + asset + ": " +
e.toString())
}
}
private fun setSecurityButtonTag(button: ImageButton, security: Int) {
when (security) {
R.drawable.box_red -> { button.tag = "red" }
R.drawable.box_yellow -> { button.tag = "yellow" }
R.drawable.box_green -> { button.tag = "green" }
}
}
external fun baresipStart(path: String)
external fun baresipStop()
external fun call_peeruri(call: String): String
external fun ua_aor(ua: String): String
external fun ua_mediaenc(ua: String): String
external fun ua_connect(ua: String, peer_uri: String): String
external fun ua_answer(ua: String, call: String)
external fun ua_hangup(ua: String, call: String, code: Int, reason: String)
external fun call_peeruri(call: String): String
external fun call_hold(call: String): Int
external fun call_unhold(call: String): Int
external fun call_send_digit(call: String, digit: Char): Int
external fun cmd_exec(cmd: String): Int
external fun reload_config(): Int
companion object {
internal var running: Boolean = false
internal var accounts = ArrayList<Account>()
var uas = ArrayList<UserAgent>()
internal var images = ArrayList<Int>()
internal var callsIn = ArrayList<Call>()
internal var callsOut = ArrayList<Call>()
internal var calls = ArrayList<Call>()
var filesPath = ""
const val EDIT_ACCOUNTS_CODE = 1
const val ACCOUNTS_CODE = 1
const val EDIT_CONTACTS_CODE = 2
const val EDIT_CONFIG_CODE = 3
const val HISTORY_CODE = 4
const val ABOUT_CODE = 5
const val ACCOUNT_CODE = 6
const val RECORD_AUDIO_PERMISSION = 1
const val HISTORY_SIZE = 100

View File

@ -0,0 +1,37 @@
package com.tutpro.baresip
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import java.util.ArrayList
class UaSpinnerAdapter(private val cxt: Context, private val uas: ArrayList<UserAgent>,
private val images: ArrayList<Int>) :
ArrayAdapter<Int>(cxt, android.R.layout.simple_spinner_item, images) {
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
private fun getImageForPosition(position: Int, parent: ViewGroup): View {
val inflater = cxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val row = inflater.inflate(R.layout.account_spinner, parent, false)
val textView = row.findViewById(R.id.spinnerText) as TextView
textView.text = uas[position].aor.replace("sip:", "")
textView.textSize = 17f
val imageView = row.findViewById(R.id.spinnerImage) as ImageView
imageView.setImageResource(images[position])
return row
}
}

View File

@ -0,0 +1,35 @@
package com.tutpro.baresip
class UserAgent (val uap: String) {
val account = Account(ua_account(uap))
val aor = ua_aor(uap)
companion object {
external fun ua_alloc(uri: String): Int
external fun ua_destroy(uri: String)
external fun ua_register(ua: String): Int
external fun ua_isregistered(ua: String): Boolean
external fun ua_unregister(ua: String)
fun userAgents(): ArrayList<UserAgent> {
val res = ArrayList<UserAgent>()
for (ua in MainActivity.uas) {
res.add(ua)
}
return res
}
fun find(uas: ArrayList<UserAgent>, uap: String): UserAgent? {
for (u in uas) {
if (u.uap == uap) return u
}
return null
}
}
}
external fun ua_account(ua: String): String
external fun ua_aor(ua: String): String

View File

@ -3,28 +3,11 @@ package com.tutpro.baresip
import android.content.Context
import android.support.v7.app.AlertDialog
import android.util.Log
import android.widget.ImageButton
import java.io.*
object Utils {
fun copyAssetToFile(context: Context, asset: String, path: String) {
try {
val `is` = context.assets.open(asset)
val os = FileOutputStream(path)
val buffer = ByteArray(512)
var byteRead: Int = `is`.read(buffer)
while (byteRead != -1) {
os.write(buffer, 0, byteRead)
byteRead = `is`.read(buffer)
}
} catch (e: IOException) {
Log.e("Baresip", "Failed to read asset " + asset + ": " +
e.toString())
}
}
fun getFileContents(file: File): String {
if (!file.exists()) {
Log.e("Baresip", "Failed to find file: " + file.path)
@ -89,46 +72,51 @@ object Utils {
return uri.substringAfter(":").substringBefore("@")
}
fun setSecurityButtonTag(button: ImageButton, security: Int) {
when (security) {
R.drawable.box_red -> { button.tag = "red" }
R.drawable.box_yellow -> { button.tag = "yellow" }
R.drawable.box_green -> { button.tag = "green" }
}
fun checkUserID(id: String): Boolean {
return Regex("^[a-zA-Z]([._-]|[a-zA-Z0-9]){1,49}\$").matches(id)
}
fun setSecurityButtonOnClickListener(context: Context, button: ImageButton, call: Call) {
button.setOnClickListener {
when (button.tag) {
"red" -> {
Utils.alertView(context, "Alert", "This call is NOT secure!")
}
"yellow" -> {
Utils.alertView(context, "Alert",
"This call is SECURE, but peer is NOT verified!")
}
"green" -> {
val unverifyDialog = AlertDialog.Builder(context)
unverifyDialog.setMessage("This call is SECURE and peer is VERIFIED! " +
"Do you want to unverify the peer?")
unverifyDialog.setPositiveButton("Unverify") { dialog, _ ->
if (cmd_exec("zrtp_unverify " + call.zid) != 0) {
Log.w("Baresip", "Command 'zrtp_unverify ${call.zid}' failed")
} else {
button.setImageResource(R.drawable.box_yellow)
button.tag = "yellow"
}
dialog.dismiss()
}
unverifyDialog.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}
unverifyDialog.create().show()
}
}
fun checkTelNo(no: String): Boolean {
return Regex("^[+]?[0-9]{1,16}\$").matches(no)
}
fun checkIP(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 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))
return false
}
return true
}
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)
}
fun implode(list: List<String>, sep: String): String {
var res = ""
for (s in list) {
if (res == "")
res = s
else
res = res + sep + s
}
return res
}
external fun cmd_exec(cmd: String): Int
external fun uri_decode(uri: String): Boolean
external fun audio_codecs(): String
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/accountRow"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/aor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textSize="20sp"
android:layout_marginBottom="6dp">
</TextView>
<ImageButton
android:id="@+id/action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_marginLeft="6dp"
android:icon="@android:drawable/ic_menu_delete"
android:scaleType="centerCrop" >
</ImageButton>
</RelativeLayout>

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/AccountView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:orientation="vertical"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="24dp" >
<TextView
android:id="@+id/DisplayNameTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="Display Name" >
</TextView>
<EditText
android:id="@+id/DisplayName"
android:layout_height="wrap_content"
android:hint="Your Name"
android:inputType="textPersonName"
android:textSize="18sp"
android:layout_width="fill_parent">
</EditText>
<TextView
android:id="@+id/AuthUserTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Authentication Username" >
</TextView>
<EditText
android:id="@+id/AuthUser"
android:layout_height="wrap_content"
android:hint="User ID"
android:textSize="18sp"
android:layout_width="fill_parent">
</EditText>
<TextView
android:id="@+id/AuthPassTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Authentication Password" >
</TextView>
<EditText
android:id="@+id/AuthPass"
android:layout_height="wrap_content"
android:hint="Password"
android:textSize="18sp"
android:inputType="textPassword"
android:layout_width="fill_parent">
</EditText>
<TextView
android:id="@+id/OutboundProxyTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Outbound Proxies" >
</TextView>
<EditText
android:id="@+id/Outbound1"
android:layout_height="wrap_content"
android:hint="SIP URI of Proxy Server"
android:textSize="18sp"
android:inputType="textUri"
android:layout_width="fill_parent">
</EditText>
<EditText
android:id="@+id/Outbound2"
android:layout_height="wrap_content"
android:hint="SIP URI of another Proxy Server"
android:textSize="18sp"
android:inputType="textUri"
android:layout_width="fill_parent">
</EditText>
<TextView
android:id="@+id/RegIntTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Registration Interval (sec)" >
</TextView>
<EditText
android:id="@+id/RegInt"
android:layout_height="wrap_content"
android:textSize="18sp"
android:inputType="number"
android:layout_width="fill_parent">
</EditText>
<TextView
android:id="@+id/AudioCodecsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Audio Codecs" >
</TextView>
<LinearLayout
android:id="@+id/CodecSpinners"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</LinearLayout>
<TextView
android:id="@+id/MediaEncTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="Media Encryption" >
</TextView>
<Spinner
android:id="@+id/mediaEncSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:popupBackground="@drawable/spinner_background" >
</Spinner>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tutpro.baresip.AccountsActivity">
<ListView
android:id="@+id/accounts"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/newAor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textSize="20sp"
android:layout_alignParentStart="true"
android:layout_marginStart="0dp"
android:hint="SIP URI user@domain" >
</EditText>
<ImageButton
android:id="@+id/addAccount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginLeft="6dp"
android:src="@drawable/action_add"
android:scaleType="centerCrop" >
</ImageButton>
</RelativeLayout>
</LinearLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/historyRow"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="0dp"