diff --git a/app/build.gradle b/app/build.gradle
index 7123bcfa..d0ea44bd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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()
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8bb08492..647068e3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,8 +51,18 @@
+
+
+
+
#
#
-;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
diff --git a/app/src/main/assets/config b/app/src/main/assets/config
index 1a1f2ac6..ca6e50a5 100644
--- a/app/src/main/assets/config
+++ b/app/src/main/assets/config
@@ -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
#------------------------------------------------------------------------------
diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c
index abece685..e10e50a8 100644
--- a/app/src/main/cpp/baresip.c
+++ b/app/src/main/cpp/baresip.c
@@ -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;
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Account.kt b/app/src/main/kotlin/com/tutpro/baresip/Account.kt
index 72fbd2fd..519f53cd 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Account.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Account.kt
@@ -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()
+ var audioCodec = ArrayList()
+ 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 {
+ var res = ArrayList()
+ for (ua in MainActivity.uas) {
+ res.add(ua.account)
+ }
+ return res
+ }
+
+ fun find(uas: ArrayList, 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, 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
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt
new file mode 100644
index 00000000..55d8ca00
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt
@@ -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()
+ 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()
+ 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(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(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()
+ 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()
+ 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(newCodecs.filter{it != ""} as ArrayList))
+ 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])
+ }
+ }
+
+}
+
diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt
new file mode 100644
index 00000000..e8388409
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt
@@ -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) :
+ ArrayAdapter(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
+ }
+
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountRow.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountRow.kt
new file mode 100644
index 00000000..3f306fd9
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/AccountRow.kt
@@ -0,0 +1,3 @@
+package com.tutpro.baresip
+
+class AccountRow(val aor: String, val action: Int)
diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt
new file mode 100644
index 00000000..3fc79bec
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt
@@ -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()
+ var posAtAccounts = ArrayList()
+
+ 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")
+ }
+ }
+
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt
index e58a5df1..f89d3863 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt
@@ -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, ua: String): ArrayList {
+ fun calls(calls: ArrayList, dir: String): ArrayList {
val result = ArrayList()
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, ua: String, call: String): Int {
+ fun uaCalls(calls: ArrayList, ua: UserAgent, dir: String): ArrayList {
+ val result = ArrayList()
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, callp: String): Call? {
+ for (c in calls) {
+ if (c.callp == callp) return c
+ }
+ return null
+ }
+
+ fun index(calls: ArrayList, 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
+ }
}
}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Codec.kt b/app/src/main/kotlin/com/tutpro/baresip/Codec.kt
new file mode 100644
index 00000000..1a8157b6
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/Codec.kt
@@ -0,0 +1,3 @@
+package com.tutpro.baresip
+
+class Codec(val name: String, val rate: Int, val channels: Int) {}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/tutpro/baresip/EditAccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/EditAccountsActivity.kt
deleted file mode 100644
index 7fb58473..00000000
--- a/app/src/main/kotlin/com/tutpro/baresip/EditAccountsActivity.kt
+++ /dev/null
@@ -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)
- }
- }
-}
-
diff --git a/app/src/main/kotlin/com/tutpro/baresip/History.kt b/app/src/main/kotlin/com/tutpro/baresip/History.kt
index f1fc8c49..ccc95d4a 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/History.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/History.kt
@@ -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()
diff --git a/app/src/main/kotlin/com/tutpro/baresip/HistoryActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/HistoryActivity.kt
index 1840bf5c..034d70ec 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/HistoryActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/HistoryActivity.kt
@@ -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) {
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
index efc58250..154f62ed 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
@@ -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()
+ var uas = ArrayList()
internal var images = ArrayList()
- internal var callsIn = ArrayList()
- internal var callsOut = ArrayList()
+ internal var calls = ArrayList()
+ 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
diff --git a/app/src/main/kotlin/com/tutpro/baresip/UaSpinnerAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/UaSpinnerAdapter.kt
new file mode 100644
index 00000000..018c8fe5
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/UaSpinnerAdapter.kt
@@ -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,
+ private val images: ArrayList) :
+ ArrayAdapter(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
+ }
+
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt
new file mode 100644
index 00000000..d22b3e85
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt
@@ -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 {
+ val res = ArrayList()
+ for (ua in MainActivity.uas) {
+ res.add(ua)
+ }
+ return res
+ }
+
+ fun find(uas: ArrayList, 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
+
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
index a7b13bab..2c56b32e 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
@@ -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, 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
}
diff --git a/app/src/main/res/drawable/action_add.png b/app/src/main/res/drawable/action_add.png
new file mode 100644
index 00000000..326d0502
Binary files /dev/null and b/app/src/main/res/drawable/action_add.png differ
diff --git a/app/src/main/res/drawable/action_remove.png b/app/src/main/res/drawable/action_remove.png
new file mode 100644
index 00000000..f397e806
Binary files /dev/null and b/app/src/main/res/drawable/action_remove.png differ
diff --git a/app/src/main/res/drawable/speaker.png b/app/src/main/res/drawable/speaker.png
new file mode 100644
index 00000000..54fa9b74
Binary files /dev/null and b/app/src/main/res/drawable/speaker.png differ
diff --git a/app/src/main/res/drawable/spinner_background.xml b/app/src/main/res/drawable/spinner_background.xml
new file mode 100644
index 00000000..a0a88be7
--- /dev/null
+++ b/app/src/main/res/drawable/spinner_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_row.xml b/app/src/main/res/layout/account_row.xml
new file mode 100644
index 00000000..a26b93a5
--- /dev/null
+++ b/app/src/main/res/layout/account_row.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
new file mode 100644
index 00000000..f2c5c39e
--- /dev/null
+++ b/app/src/main/res/layout/activity_account.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_accounts.xml b/app/src/main/res/layout/activity_accounts.xml
new file mode 100644
index 00000000..a405cac6
--- /dev/null
+++ b/app/src/main/res/layout/activity_accounts.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/history_row.xml b/app/src/main/res/layout/history_row.xml
index f03231c2..00a9f25e 100644
--- a/app/src/main/res/layout/history_row.xml
+++ b/app/src/main/res/layout/history_row.xml
@@ -1,6 +1,6 @@