diff --git a/app/build.gradle b/app/build.gradle index 88183be2..bea9a4e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId = 'com.tutpro.baresip' minSdkVersion 21 targetSdkVersion 27 - versionCode = 33 - versionName = '4.0.0' + versionCode = 34 + versionName = '4.1.0' externalNativeBuild { cmake { cFlags '-DHAVE_INTTYPES_H' diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c index 5233887d..690f2d6f 100644 --- a/app/src/main/cpp/baresip.c +++ b/app/src/main/cpp/baresip.c @@ -700,7 +700,7 @@ Java_com_tutpro_baresip_AccountKt_account_1sipnat(JNIEnv *env, jobject thiz, jst JNIEXPORT jint JNICALL Java_com_tutpro_baresip_AccountKt_account_1set_1sipnat(JNIEnv *env, jobject thiz, - jstring javaAcc, jstring javaSipNat) { + jstring javaAcc, jstring javaSipNat) { 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); @@ -716,7 +716,7 @@ Java_com_tutpro_baresip_AccountKt_account_1set_1sipnat(JNIEnv *env, jobject thiz JNIEXPORT jint JNICALL Java_com_tutpro_baresip_AccountKt_account_1set_1mwi(JNIEnv *env, jobject thiz, - jstring javaAcc, jstring javaValue) { + jstring javaAcc, jstring javaValue) { 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); @@ -748,8 +748,7 @@ Java_com_tutpro_baresip_AccountKt_account_1vm_1uri(JNIEnv *env, jobject thiz, js } JNIEXPORT jstring JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1alloc(JNIEnv *env, jobject thiz, - jstring javaUri) { +Java_com_tutpro_baresip_Api_ua_1alloc(JNIEnv *env, jobject thiz, jstring javaUri) { const char *uri = (*env)->GetStringUTFChars(env, javaUri, 0); struct ua *ua; LOGD("allocating UA '%s'\n", uri); @@ -762,8 +761,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1alloc(JNIEnv *env, jobject } JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1register(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_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); @@ -772,8 +770,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1register(JNIEnv *env, jobje } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1unregister(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_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); @@ -782,8 +779,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1unregister(JNIEnv *env, job } JNIEXPORT jboolean JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1isregistered(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_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); @@ -792,8 +788,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1isregistered(JNIEnv *env, j } JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1update_1account(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_ua_1update_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); @@ -802,8 +797,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1update_1account(JNIEnv *env } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1destroy(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_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); @@ -812,7 +806,7 @@ Java_com_tutpro_baresip_UserAgent_00024Companion_ua_1destroy(JNIEnv *env, jobjec } JNIEXPORT jstring JNICALL -Java_com_tutpro_baresip_UserAgentKt_ua_1account(JNIEnv *env, jobject thiz, jstring javaUA) +Java_com_tutpro_baresip_Api_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); @@ -828,8 +822,7 @@ Java_com_tutpro_baresip_UserAgentKt_ua_1account(JNIEnv *env, jobject thiz, jstri } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_uag_1current_1set(JNIEnv *env, jobject thiz, - jstring javaUA) +Java_com_tutpro_baresip_Api_uag_1current_1set(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); @@ -849,9 +842,47 @@ Java_com_tutpro_baresip_Api_call_1peeruri(JNIEnv *env, jobject thiz, jstring jav return (*env)->NewStringUTF(env, call_peeruri(call)); } +JNIEXPORT jint JNICALL +Java_com_tutpro_baresip_Api_call_1send_1digit(JNIEnv *env, jobject thiz, jstring javaCall, + jchar digit) { + const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); + const uint16_t native_digit = digit; + struct call *call = (struct call *)strtoul(native_call, NULL, 10); + LOGD("sending DTMF digit '%c' to call %s\n", (char)native_digit, native_call); + re_thread_enter(); + int res = call_send_digit(call, (char)native_digit); + if (!res) res = call_send_digit(call, KEYCODE_REL); + re_thread_leave(); + (*env)->ReleaseStringUTFChars(env, javaCall, native_call); + return res; +} + +JNIEXPORT void JNICALL +Java_com_tutpro_baresip_Api_ua_1hangup(JNIEnv *env, jobject thiz, + jstring javaUA, jstring javaCall, jint code, + jstring reason) { + const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0); + const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); + struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10); + struct call *call = (struct call *)strtoul(native_call, NULL, 10); + const uint16_t native_code = code; + const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0); + LOGD("hanging up call %s/%s\n", native_ua, native_call); + re_thread_enter(); + if (strlen(native_reason) == 0) + ua_hangup(ua, call, native_code, NULL); + else + ua_hangup(ua, call, native_code, native_reason); + re_thread_leave(); + (*env)->ReleaseStringUTFChars(env, javaUA, native_ua); + (*env)->ReleaseStringUTFChars(env, javaCall, native_call); + (*env)->ReleaseStringUTFChars(env, reason, native_reason); + return; +} + JNIEXPORT jstring JNICALL -Java_com_tutpro_baresip_MainActivity_ua_1connect(JNIEnv *env, jobject thiz, - jstring javaUA, jstring javaURI) { +Java_com_tutpro_baresip_Api_ua_1connect(JNIEnv *env, jobject thiz, jstring javaUA, + jstring javaURI) { struct call *call; struct ua *ua; int err; @@ -875,8 +906,8 @@ Java_com_tutpro_baresip_MainActivity_ua_1connect(JNIEnv *env, jobject thiz, } JNIEXPORT jstring JNICALL -Java_com_tutpro_baresip_MainActivity_ua_1call_1alloc(JNIEnv *env, jobject thiz, - jstring javaUA, jstring javaXCall) { +Java_com_tutpro_baresip_Api_ua_1call_1alloc(JNIEnv *env, jobject thiz, jstring javaUA, + jstring javaXCall) { struct call *xcall, *call = NULL; struct ua *ua; int err; @@ -901,8 +932,8 @@ Java_com_tutpro_baresip_MainActivity_ua_1call_1alloc(JNIEnv *env, jobject thiz, } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_ua_1answer(JNIEnv *env, jobject thiz, - jstring javaUA, jstring javaCall) { +Java_com_tutpro_baresip_Api_ua_1answer(JNIEnv *env, jobject thiz, jstring javaUA, + jstring javaCall) { const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0); const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); LOGD("answering call %s/%s\n", native_ua, native_call); @@ -918,8 +949,8 @@ Java_com_tutpro_baresip_MainActivity_ua_1answer(JNIEnv *env, jobject thiz, } JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_MainActivity_call_1connect(JNIEnv *env, jobject thiz, jstring javaCall, - jstring javaPeer) { +Java_com_tutpro_baresip_Api_call_1connect(JNIEnv *env, jobject thiz, jstring javaCall, + jstring javaPeer) { const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); const char *native_peer = (*env)->GetStringUTFChars(env, javaPeer, 0); struct call *call = (struct call *)strtoul(native_call, NULL, 10); @@ -936,7 +967,23 @@ Java_com_tutpro_baresip_MainActivity_call_1connect(JNIEnv *env, jobject thiz, js } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_call_1start_1audio(JNIEnv *env, jobject thiz, jstring javaCall) { +Java_com_tutpro_baresip_Api_call_1notify_1sipfrag(JNIEnv *env, jobject thiz, jstring javaCall, + jint code, jstring reason) { + const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); + struct call *call = (struct call *)strtoul(native_call, NULL, 10); + const uint16_t native_code = code; + const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0); + LOGD("notifying call %s/%s\n", native_call, native_reason); + re_thread_enter(); + (void)call_notify_sipfrag(call, native_code, native_reason); + re_thread_leave(); + (*env)->ReleaseStringUTFChars(env, javaCall, native_call); + (*env)->ReleaseStringUTFChars(env, reason, native_reason); + return; +} + +JNIEXPORT void JNICALL +Java_com_tutpro_baresip_Api_call_1start_1audio(JNIEnv *env, jobject thiz, jstring javaCall) { const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); struct call *call = (struct call *)strtoul(native_call, NULL, 10); LOGD("starting audio of call %s\n", native_call); @@ -949,7 +996,7 @@ Java_com_tutpro_baresip_MainActivity_call_1start_1audio(JNIEnv *env, jobject thi } JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_call_1stop_1audio(JNIEnv *env, jobject thiz, jstring javaCall) { +Java_com_tutpro_baresip_Api_call_1stop_1audio(JNIEnv *env, jobject thiz, jstring javaCall) { const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); struct call *call = (struct call *)strtoul(native_call, NULL, 10); LOGD("stopping audio of call %s\n", native_call); @@ -962,8 +1009,7 @@ Java_com_tutpro_baresip_MainActivity_call_1stop_1audio(JNIEnv *env, jobject thiz } JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *env, jobject thiz, - jstring javaCall) { +Java_com_tutpro_baresip_Api_call_1hold(JNIEnv *env, jobject thiz, jstring javaCall) { const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); struct call *call = (struct call *) strtoul(native_call, NULL, 10); int ret; @@ -979,7 +1025,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) { +Java_com_tutpro_baresip_Api_call_1unhold(JNIEnv *env, jobject thiz, jstring javaCall) { const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); struct call *call = (struct call *) strtoul(native_call, NULL, 10); int ret; @@ -994,48 +1040,9 @@ Java_com_tutpro_baresip_MainActivity_call_1unhold(JNIEnv *env, jobject thiz, jst return ret; } -JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_ua_1hangup(JNIEnv *env, jobject thiz, - jstring javaUA, jstring javaCall, jint code, - jstring reason) { - const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0); - const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); - struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10); - struct call *call = (struct call *)strtoul(native_call, NULL, 10); - const uint16_t native_code = code; - const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0); - LOGD("hanging up call %s/%s\n", native_ua, native_call); - re_thread_enter(); - if (strlen(native_reason) == 0) - ua_hangup(ua, call, native_code, NULL); - else - ua_hangup(ua, call, native_code, native_reason); - re_thread_leave(); - (*env)->ReleaseStringUTFChars(env, javaUA, native_ua); - (*env)->ReleaseStringUTFChars(env, javaCall, native_call); - (*env)->ReleaseStringUTFChars(env, reason, native_reason); - return; -} - JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_MainActivity_call_1send_1digit(JNIEnv *env, jobject thiz, - jstring javaCall, jchar digit) { - const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); - const uint16_t native_digit = digit; - struct call *call = (struct call *)strtoul(native_call, NULL, 10); - LOGD("sending DTMF digit '%c' to call %s\n", (char)native_digit, native_call); - re_thread_enter(); - int res = call_send_digit(call, (char)native_digit); - if (!res) res = call_send_digit(call, KEYCODE_REL); - re_thread_leave(); - (*env)->ReleaseStringUTFChars(env, javaCall, native_call); - return res; -} - -JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_ChatActivity_message_1send(JNIEnv *env, jobject thiz, jstring javaUA, - jstring javaPeer, jstring javaMsg, - jstring javaTime) { +Java_com_tutpro_baresip_Api_message_1send(JNIEnv *env, jobject thiz, jstring javaUA, + jstring javaPeer, jstring javaMsg, jstring javaTime) { struct ua *ua; const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0); const char *native_peer = (*env)->GetStringUTFChars(env, javaPeer, 0); @@ -1055,24 +1062,8 @@ Java_com_tutpro_baresip_ChatActivity_message_1send(JNIEnv *env, jobject thiz, js return err; } -JNIEXPORT void JNICALL -Java_com_tutpro_baresip_MainActivity_call_1notify_1sipfrag(JNIEnv *env, jobject thiz, - jstring javaCall, jint code, jstring reason) { - const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); - struct call *call = (struct call *)strtoul(native_call, NULL, 10); - const uint16_t native_code = code; - const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0); - LOGD("notifying call %s/%s\n", native_call, native_reason); - re_thread_enter(); - (void)call_notify_sipfrag(call, native_code, native_reason); - re_thread_leave(); - (*env)->ReleaseStringUTFChars(env, javaCall, native_call); - (*env)->ReleaseStringUTFChars(env, reason, native_reason); - return; -} - JNIEXPORT jint JNICALL -Java_com_tutpro_baresip_MainActivity_reload_1config(JNIEnv *env, jobject thiz) { +Java_com_tutpro_baresip_Api_reload_1config(JNIEnv *env, jobject thiz) { int err; err = conf_configure(); if (err) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Account.kt b/app/src/main/kotlin/com/tutpro/baresip/Account.kt index 2e9fdf6e..966bdb54 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Account.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Account.kt @@ -116,28 +116,28 @@ class Account(val accp: String) { fun accounts(): ArrayList { val res = ArrayList() - for (ua in MainActivity.uas) { + for (ua in UserAgent.uas()) { res.add(ua.account) } return res } - fun find(uas: ArrayList, accp: String): Account? { - for (ua in uas) { + fun find(accp: String): Account? { + for (ua in UserAgent.uas()) { if (ua.account.accp == accp) return ua.account } return null } fun findUa(aor: String): UserAgent? { - for (ua in MainActivity.uas) { + for (ua in UserAgent.uas()) { if (ua.account.aor == aor) return ua } return null } - fun exists(uas: ArrayList, aor: String): Boolean { - for (ua in uas) { + fun exists(aor: String): Boolean { + for (ua in UserAgent.uas()) { if (ua.account.aor == aor) return true } return false @@ -155,7 +155,6 @@ 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_sipnat(acc: String): String external fun account_set_sipnat(acc: String, sipnat: String): Int external fun account_audio_codec(acc: String, ix: Int): String external fun account_regint(acc: String): Int diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt index 6b341bef..4b2d7a8e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountActivity.kt @@ -32,9 +32,9 @@ class AccountActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_account) - acc = Account.find(MainActivity.uas, intent.extras.getString("accp"))!! + acc = Account.find(intent.extras.getString("accp"))!! aor = acc.aor - uaIndex = UserAgent.findAorIndex(MainActivity.uas, aor)!! + uaIndex = UserAgent.findAorIndex(UserAgent.uas(), aor)!! setTitle(aor.replace("sip:", "")) @@ -220,7 +220,7 @@ class AccountActivity : AppCompatActivity() { if (acc.regint != 3600) newRegint = 3600 } else { if (acc.regint != 0) { - UserAgent.ua_unregister(MainActivity.uas[uaIndex].uap) + Api.ua_unregister(UserAgent.uas()[uaIndex].uap) newRegint = 0 } } @@ -285,20 +285,18 @@ class AccountActivity : AppCompatActivity() { if (save) { AccountsActivity.saveAccounts() - if (UserAgent.ua_update_account(MainActivity.uas[uaIndex].uap) != 0) + if (Api.ua_update_account(UserAgent.uas()[uaIndex].uap) != 0) Log.e("Baresip", "Failed to update UA with AoR $aor") } - if (regCheck.isChecked) UserAgent.ua_register(MainActivity.uas[uaIndex].uap) + if (regCheck.isChecked) Api.ua_register(UserAgent.uas()[uaIndex].uap) - setResult(RESULT_OK, intent) finish() return true } else if (item.itemId == android.R.id.home) { Log.d("Baresip", "Back array was pressed at Account") - setResult(RESULT_CANCELED, intent) finish() return true diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt index ef73a1ec..3961921a 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountListAdapter.kt @@ -1,5 +1,6 @@ package com.tutpro.baresip +import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle @@ -28,22 +29,21 @@ class AccountListAdapter(private val cxt: Context, private val rows: ArrayList val i = Intent(cxt, AccountActivity::class.java) val b = Bundle() - b.putString("accp", MainActivity.uas[position].account.accp) + b.putString("accp", UserAgent.uas()[position].account.accp) i.putExtras(b) - cxt.startActivity(i) + (cxt as Activity).startActivity(i) } + val ua = UserAgent.uas()[position] 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].account.aor}?") + deleteDialog.setMessage("Do you want to delete account ${ua.account.aor}?") deleteDialog.setPositiveButton("Delete") { dialog, _ -> - MainActivity.uas[position].destroy() - MainActivity.uas.remove(MainActivity.uas[position]) - MainActivity.images.removeAt(position) - AccountsActivity.saveAccounts() + UserAgent.remove(ua) AccountsActivity.generateAccounts() + AccountsActivity.saveAccounts() this.notifyDataSetChanged() dialog.dismiss() } diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt index c9978af2..52f86a7b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt @@ -13,17 +13,20 @@ import java.util.ArrayList class AccountsActivity : AppCompatActivity() { - internal var aor: String = "" internal lateinit var alAdapter: AccountListAdapter + internal var aor = "" + public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_accounts) - val listview = findViewById(R.id.accounts) as ListView + filesPath = applicationContext.filesDir.absolutePath + + val listView = findViewById(R.id.accounts) as ListView generateAccounts() alAdapter = AccountListAdapter(this, accounts) - listview.adapter = alAdapter + listView.adapter = alAdapter val addAccountButton = findViewById(R.id.addAccount) as ImageButton val newAorView = findViewById(R.id.newAor) as EditText @@ -34,7 +37,7 @@ class AccountsActivity : AppCompatActivity() { Log.e("Baresip", "Invalid SIP Address of Record $aor") Utils.alertView(this, "Notice", "Invalid SIP Address of Record: $aor") - } else if (Account.exists(MainActivity.uas, aor)) { + } else if (Account.exists(aor)) { Log.e("Baresip", "Account $aor already exists") } else { val ua = UserAgent.uaAlloc("<$aor>;regq=0.5;pubint=0;regint=0") @@ -46,8 +49,7 @@ class AccountsActivity : AppCompatActivity() { newAorView.setText("") newAorView.hint = "SIP URI user@domain" newAorView.clearFocus() - MainActivity.uas.add(ua) - MainActivity.images.add(R.drawable.dot_yellow) + UserAgent.add(ua, R.drawable.dot_yellow) generateAccounts() alAdapter.notifyDataSetChanged() saveAccounts() @@ -56,10 +58,18 @@ class AccountsActivity : AppCompatActivity() { b.putString("accp", ua.account.accp) i.putExtras(b) startActivity(i) - // recreate() } } } + + val accp = intent.extras.getString("accp") + if (accp != "") { + val i = Intent(this, AccountActivity::class.java) + val b = Bundle() + b.putString("accp", accp) + i.putExtras(b) + startActivity(i) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -76,11 +86,13 @@ class AccountsActivity : AppCompatActivity() { companion object { + internal var filesPath = "" + var accounts = ArrayList() fun generateAccounts() { accounts.clear() - for (ua in MainActivity.uas) + for (ua in UserAgent.uas()) accounts.add(AccountRow(ua.account.aor.replace("sip:", ""), R.drawable.action_remove)) } @@ -88,9 +100,8 @@ class AccountsActivity : AppCompatActivity() { 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") + Utils.putFileContents(File(filesPath + "/accounts"), accounts) + // Log.d("Baresip", "Saved accounts '${accounts}' to '${filesPath}/accounts") } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Api.kt b/app/src/main/kotlin/com/tutpro/baresip/Api.kt index a906d219..aab690f6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Api.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Api.kt @@ -3,7 +3,28 @@ package com.tutpro.baresip object Api { external fun audio_codecs(): String + external fun uag_current_set(uap: String) + external fun ua_account(ua: String): String + external fun ua_alloc(uri: String): String + external fun ua_update_account(ua: String): Int + external fun ua_destroy(ua: String) + external fun ua_register(ua: String): Int + external fun ua_isregistered(ua: String): Boolean + external fun ua_unregister(ua: String) + external fun ua_connect(uap: String, peer_uri: String): String + external fun ua_hangup(uap: String, callp: String, code: Int, reason: String) + external fun ua_call_alloc(uap: String, xcallp: String): String + external fun ua_answer(uap: String, callp: String) + external fun call_hold(callp: String): Int + external fun call_unhold(callp: String): Int + external fun call_connect(callp: String, peer_uri: String): Int external fun call_peeruri(callp: String): String + external fun call_send_digit(callp: String, digit: Char): Int + external fun call_notify_sipfrag(callp: String, code: Int, reason: String) + external fun call_start_audio(callp: String) + external fun call_stop_audio(callp: String) + external fun message_send(uap: String, peer_uri: String, message: String, time: String): Int + external fun reload_config(): Int external fun cmd_exec(cmd: String): Int external fun contact_add(contact: String) external fun contacts_remove() diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 0bb6bc92..11d4f6ab 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -1,11 +1,10 @@ package com.tutpro.baresip +import android.annotation.TargetApi import android.app.* import android.app.Notification.VISIBILITY_PUBLIC -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter +import android.content.* +import android.media.* import android.net.ConnectivityManager import android.net.NetworkInfo import android.net.wifi.WifiManager @@ -22,11 +21,14 @@ import android.os.Build import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE import java.nio.charset.StandardCharsets import java.io.InputStream +import java.util.* class BaresipService: Service() { private val LOG_TAG = "Baresip Service" internal lateinit var intent: Intent + internal lateinit var am: AudioManager + internal lateinit var rt: Ringtone internal lateinit var nm: NotificationManager internal lateinit var snb: NotificationCompat.Builder internal lateinit var npi: PendingIntent @@ -34,6 +36,10 @@ class BaresipService: Service() { internal lateinit var wl: PowerManager.WakeLock internal lateinit var fl: WifiManager.WifiLock + internal var rtTimer: Timer? = null + internal var filesPath = "" + internal var restarting = false + override fun onCreate() { Log.d(LOG_TAG, "At onCreate") @@ -41,6 +47,13 @@ class BaresipService: Service() { intent = Intent("com.tutpro.baresip.EVENT") intent.setPackage("com.tutpro.baresip") + filesPath = applicationContext.filesDir.absolutePath + + am = getSystemService(Context.AUDIO_SERVICE) as AudioManager + val rtUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext, + RingtoneManager.TYPE_RINGTONE) + rt = RingtoneManager.getRingtone(applicationContext, rtUri) + nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager createNotificationChannels() snb = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID) @@ -59,7 +72,7 @@ class BaresipService: Service() { if (isConnected) { Log.d(LOG_TAG, "Network is connected/connecting") if (disconnected) { - UserAgent.register(MainActivity.uas) + UserAgent.register(uas) disconnected = false } } else { @@ -103,22 +116,21 @@ class BaresipService: Service() { val assets = arrayOf("accounts", "contacts", "config", "busy.wav", "callwaiting.wav", "error.wav", "notfound.wav", "ring.wav", "ringback.wav") - val path = applicationContext.filesDir.path - var file = File(path) + var file = File(filesPath) if (!file.exists()) { Log.d(LOG_TAG, "Creating baresip directory") try { - File(path).mkdirs() + File(filesPath).mkdirs() } catch (e: Error) { Log.e(LOG_TAG, "Failed to create directory: " + e.toString()) } } for (a in assets) { - file = File("$path/$a") + file = File("${filesPath}/$a") if (!file.exists()) { Log.d(LOG_TAG, "Copying asset $a") - Utils.copyAssetToFile(applicationContext, a, "$path/$a") + Utils.copyAssetToFile(applicationContext, a, "$filesPath/$a") } else { Log.d(LOG_TAG, "Asset $a already copied") if (a == "config") { @@ -144,8 +156,8 @@ class BaresipService: Service() { } wl.acquire() - Thread(Runnable { baresipStart(path) }).start() - BaresipService.IS_SERVICE_RUNNING = true + Thread(Runnable { baresipStart(filesPath) }).start() + BaresipService.isServiceRunning = true registerReceiver(nr, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")) showStatusNotification() } @@ -159,7 +171,7 @@ class BaresipService: Service() { } "Kill" -> { - RESTARTING = true + restarting = true stop() } } @@ -175,7 +187,7 @@ class BaresipService: Service() { Log.d(LOG_TAG, "In onDestroy") super.onDestroy() Log.i(LOG_TAG, "Restart baresip killed by Android") - RESTARTING = true + restarting = true stop() } @@ -208,14 +220,13 @@ class BaresipService: Service() { fun uaAdd(uap: String) { Log.d(LOG_TAG, "addUA at BaresipService") val ua = UserAgent(uap) - MainActivity.uas.add(ua) - if (UserAgent.ua_isregistered(uap)) { + uas.add(ua) + if (Api.ua_isregistered(uap)) { Log.d(LOG_TAG, "Ua ${ua.account.aor} is registered") - MainActivity.images.add(R.drawable.dot_green) + status.add(R.drawable.dot_green) } else { Log.d(LOG_TAG, "Ua ${ua.account.aor} is NOT registered") - MainActivity.images.add(R.drawable.dot_yellow) - // ua.register() + status.add(R.drawable.dot_yellow) } val intent = Intent("service event") intent.putExtra("event", "ua added") @@ -226,54 +237,69 @@ class BaresipService: Service() { @Keep fun uaEvent(event: String, uap: String, callp: String) { - Log.d(LOG_TAG, "updateStatus got event $event/$uap/$callp") - if (!IS_SERVICE_RUNNING) return + Log.d(LOG_TAG, "uaEvent got event $event/$uap/$callp") + if (!isServiceRunning) return if (event == "exit") return - val ua = UserAgent.find(MainActivity.uas, uap) + val ua = UserAgent.find(uap) if (ua == null) { - Log.w(LOG_TAG, "updateStatus did not find ua $uap") + Log.w(LOG_TAG, "uaEvent did not find ua $uap") return } val aor = ua.account.aor - for (account_index in MainActivity.uas.indices) { - if (MainActivity.uas[account_index].account.aor == aor) { - when (event) { + var newEvent: String? = null + val ev = event.split(",") + for (account_index in uas.indices) { + if (uas[account_index].account.aor == aor) { + when (ev[0]) { "registering" -> { return } "registered" -> { if (ua.account.regint == 0) - MainActivity.images[account_index] = R.drawable.dot_yellow + status[account_index] = R.drawable.dot_yellow else - MainActivity.images[account_index] = R.drawable.dot_green + status[account_index] = R.drawable.dot_green updateStatusNotification() } "registering failed" -> { - MainActivity.images[account_index] = R.drawable.dot_red + status[account_index] = R.drawable.dot_red updateStatusNotification() } "unregistering" -> { - MainActivity.images[account_index] = R.drawable.dot_yellow + status[account_index] = R.drawable.dot_yellow updateStatusNotification() } "call incoming" -> { - if (!Utils.isVisible()) { + val peerUri = Api.call_peeruri(callp) + if (Call.calls().size > 0) { + Log.i(LOG_TAG, "Auto-rejecting incoming call $uap/$callp/$peerUri") + Api.ua_hangup(uap, callp, 486, "Busy Here") + CallHistory.add(CallHistory(aor, peerUri, "in", false)) + CallHistory.save(filesPath) + ua.account.missedCalls = true + newEvent = "call rejected" + } else { + Log.d(LOG_TAG, "Incoming call $uap/$callp/$peerUri") + calls.add(Call(callp, ua, peerUri, "in", "incoming", + Utils.dtmfWatcher(callp))) + startRinging() + } + if ((newEvent == null) && !Utils.isVisible()) { val cnb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) + val caller = Utils.friendlyUri(ContactsActivity.contactName(peerUri), + Utils.aorDomain(aor)) + val title = "Incoming call from $caller" cnb.setSmallIcon(R.drawable.ic_stat) .setColor(0x0ca1fd) .setContentIntent(npi) .setAutoCancel(true) - .setContentTitle("Call from ${ContactsActivity.contactName( - Api.call_peeruri(callp))}") + .setContentTitle(title) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { cnb.setVibrate(LongArray(0)) .setVisibility(VISIBILITY_PRIVATE) .setPriority(Notification.PRIORITY_HIGH) } - /* val view = RemoteViews(getPackageName(), R.layout.call_notification) - view.setTextViewText(R.id.callFrom, "Call from ${Api.call_peeruri(callp)}") - cnb.setContent(view) */ val answerIntent = Intent(this, MainActivity::class.java) answerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -288,22 +314,114 @@ class BaresipService: Service() { rejectIntent.putExtra("callp", callp) val rejectPendingIntent = PendingIntent.getActivity(this, 1, rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT) - /* view.setOnClickPendingIntent(R.id.answerButton, answerPendingIntent) - view.setOnClickPendingIntent(R.id.rejectButton, rejectPendingIntent) - cnb.setCustomBigContentView(view) */ cnb.addAction(R.drawable.ic_stat, "Answer", answerPendingIntent) cnb.addAction(R.drawable.ic_stat, "Reject", rejectPendingIntent) nm.notify(CALL_NOTIFICATION_ID, cnb.build()) } } - "call established", "call closed" -> { + "call established" -> { nm.cancel(CALL_NOTIFICATION_ID) + val call = Call.find(callp) + if (call == null) { + Log.w(LOG_TAG, "Established AoR $aor call $callp is not found") + return + } + Log.d("Baresip", "AoR $aor call $callp established") + call.status = "connected" + call.onhold = false + call.security = R.drawable.box_red + CallHistory.add(CallHistory(aor, call.peerURI, call.dir, true)) + CallHistory.save(filesPath) + call.hasHistory = true + if (rtTimer != null) { + rtTimer!!.cancel() + rtTimer = null + if (rt.isPlaying) rt.stop() + } + am.mode = AudioManager.MODE_IN_COMMUNICATION + requestAudioFocus( AudioManager.STREAM_VOICE_CALL) + am.isSpeakerphoneOn = false + } + "call transfer" -> { + val call = Call.find(callp) + if (call == null) { + Log.d("Baresip","AoR $aor call $callp to be transferred is not found") + return + } + if (!Utils.isVisible()) { + val cnb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) + val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]), + Utils.aorDomain(aor)) + val title = "Call transfer request to $target" + cnb.setSmallIcon(R.drawable.ic_stat) + .setColor(0x0ca1fd) + .setContentIntent(npi) + .setDefaults(Notification.DEFAULT_SOUND) + .setAutoCancel(true) + .setContentTitle(title) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + cnb.setVibrate(LongArray(0)) + .setVisibility(VISIBILITY_PRIVATE) + .setPriority(Notification.PRIORITY_HIGH) + } + val acceptIntent = Intent(this, MainActivity::class.java) + acceptIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + acceptIntent.putExtra("action", "transfer") + acceptIntent.putExtra("uap", uap) + acceptIntent.putExtra("callp", callp) + acceptIntent.putExtra("uri", ev[1]) + val acceptPendingIntent = PendingIntent.getActivity(this, + 0, acceptIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val rejectIntent = Intent(this, MainActivity::class.java) + rejectIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP) + rejectIntent.putExtra("action", "deny") + rejectIntent.putExtra("callp", callp) + val rejectPendingIntent = PendingIntent.getActivity(this, + 1, rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT) + cnb.addAction(R.drawable.ic_stat, "Accept", acceptPendingIntent) + cnb.addAction(R.drawable.ic_stat, "Deny", rejectPendingIntent) + nm.notify(CALL_NOTIFICATION_ID, cnb.build()) + return + } + } + "call closed" -> { + nm.cancel(CALL_NOTIFICATION_ID) + val call = Call.find(callp) + if (call == null) { + Log.d("Baresip","AoR $aor call $callp that is closed is not found") + return + } + Log.d("Baresip", "AoR $aor call $callp is closed") + stopRinging() + calls.remove(call) + if (!call.hasHistory) { + CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false)) + CallHistory.save(filesPath) + if (call.dir == "in") ua.account.missedCalls = true + } + if (Call.calls().size == 0) { + am.mode = AudioManager.MODE_NORMAL + } else { + val uaCalls = Call.uaCalls(ua, "") + if (uaCalls.size > 0) + Api.call_start_audio(uaCalls[uaCalls.size - 1].callp) + } + if (am.isSpeakerphoneOn) am.isSpeakerphoneOn = false + if (audioFocused) abandonAudioFocus() + } + "transfer failed" -> { + Log.d(LOG_TAG, "AoR $aor hanging up call $callp with ${ev[1]}") + Api.ua_hangup(uap, callp, 0, "") + return } } } } + if (newEvent == null) newEvent = event val intent = Intent("service event") - intent.putExtra("event", event) + intent.putExtra("event", newEvent) intent.putExtra("params", arrayListOf(uap, callp)) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) } @@ -316,25 +434,29 @@ class BaresipService: Service() { } catch (e: Exception) { Log.e(LOG_TAG, "UTF-8 decode failed") } - Log.d(LOG_TAG, "Message event $uap/$peer/$s") + Log.d(LOG_TAG, "Message event for $uap from $peer") + val ua = UserAgent.find(uap) + if (ua == null) { + Log.w(LOG_TAG, "messageEvent did not find ua $uap") + return + } val timeStamp = System.currentTimeMillis().toString() if (!Utils.isVisible()) { val cnb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) + val sender = Utils.friendlyUri(ContactsActivity.contactName(peer), + Utils.aorDomain(ua.account.aor)) cnb.setSmallIcon(R.drawable.ic_stat) .setColor(0x0ca1fd) .setContentIntent(npi) .setDefaults(Notification.DEFAULT_SOUND) .setAutoCancel(true) - .setContentTitle("Message from ${ContactsActivity.contactName(peer)}") + .setContentTitle("Message from $sender") .setContentText(s) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { cnb.setVibrate(LongArray(0)) .setVisibility(VISIBILITY_PRIVATE) .setPriority(Notification.PRIORITY_HIGH) } - /* val view = RemoteViews(getPackageName(), R.layout.call_notification) - view.setTextViewText(R.id.callFrom, "Call from ${Api.call_peeruri(callp)}") - cnb.setContent(view) */ val replyIntent = Intent(this, MainActivity::class.java) replyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -359,9 +481,6 @@ class BaresipService: Service() { deleteIntent.putExtra("time", timeStamp) val deletePendingIntent = PendingIntent.getActivity(this, 2, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT) - /* view.setOnClickPendingIntent(R.id.answerButton, answerPendingIntent) - view.setOnClickPendingIntent(R.id.rejectButton, rejectPendingIntent) - cnb.setCustomBigContentView(view) */ cnb.addAction(R.drawable.ic_stat, "Reply", replyPendingIntent) cnb.addAction(R.drawable.ic_stat, "Save", savePendingIntent) cnb.addAction(R.drawable.ic_stat, "Delete", deletePendingIntent) @@ -385,28 +504,28 @@ class BaresipService: Service() { @Keep fun stopped() { Log.d(LOG_TAG, "got event 'stopped'") - IS_SERVICE_RUNNING = false + isServiceRunning = false val intent = Intent("service event") intent.putExtra("event", "stopped") intent.putExtra("params", arrayListOf()) LocalBroadcastManager.getInstance(this).sendBroadcast(intent) stopForeground(true) stopSelf() - if (RESTARTING) restart() + if (restarting) restart() } private fun updateStatusNotification() { val contentView = RemoteViews(getPackageName(), R.layout.status_notification) for (i: Int in 0 .. 5) { val resID = resources.getIdentifier("status$i", "id", packageName) - if (i < MainActivity.images.size) { - contentView.setImageViewResource(resID, MainActivity.images[i]) + if (i < status.size) { + contentView.setImageViewResource(resID, status[i]) contentView.setViewVisibility(resID, View.VISIBLE) } else { contentView.setViewVisibility(resID, View.INVISIBLE) } } - if (MainActivity.images.size > 4) + if (status.size > 4) contentView.setViewVisibility(R.id.etc, View.VISIBLE) else contentView.setViewVisibility(R.id.etc, View.INVISIBLE) @@ -414,11 +533,78 @@ class BaresipService: Service() { nm.notify(STATUS_NOTIFICATION_ID, snb.build()) } + private fun requestAudioFocus(streamType: Int) { + val res: Int + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && (audioFocusRequest == null)) { + val playbackAttributes = AudioAttributes.Builder() + .setLegacyStreamType(streamType) + .build() + @TargetApi(26) + audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) + .setAudioAttributes(playbackAttributes) + .build() + @TargetApi(26) + res = am.requestAudioFocus(audioFocusRequest) + if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.d("Baresip", "Audio focus granted") + } else { + Log.d("Baresip", "Audio focus denied") + audioFocusRequest = null + } + } else { + res = am.requestAudioFocus(null, streamType, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) + if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.d("Baresip", "Audio focus granted") + audioFocused = true + } else { + Log.d("Baresip", "Audio focus denied") + audioFocused = false + } + } + } + + private fun abandonAudioFocus() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (audioFocusRequest != null) { + am.abandonAudioFocusRequest(audioFocusRequest) + audioFocusRequest = null + } + } else { + if (audioFocused) { + am.abandonAudioFocus(null) + audioFocused = false + } + } + } + + private fun startRinging() { + am.mode = AudioManager.MODE_RINGTONE + requestAudioFocus(AudioManager.STREAM_RING) + rt.play() + rtTimer = Timer() + rtTimer!!.scheduleAtFixedRate(object : TimerTask() { + override fun run() { + if (!rt.isPlaying()) { + rt.play() + } + } + }, 1000 * 1, 1000 * 1) + } + + private fun stopRinging() { + if (rtTimer != null) { + rtTimer!!.cancel() + rtTimer = null + if (rt.isPlaying) rt.stop() + } + } + private fun stop() { - MainActivity.uas.clear() - MainActivity.images.clear() - MainActivity.history.clear() - MainActivity.messages.clear() + uas.clear() + status.clear() + history.clear() + messages.clear() try { unregisterReceiver(nr) } catch (e: Exception) { @@ -427,15 +613,15 @@ class BaresipService: Service() { if (this::nm.isInitialized) nm.cancelAll() if (this::wl.isInitialized && wl.isHeld) wl.release() if (this::fl.isInitialized && fl.isHeld) fl.release() - if (IS_SERVICE_RUNNING) { + if (isServiceRunning) { baresipStop(forceStop) if (!forceStop) forceStop = true - } else if (RESTARTING) + } else if (restarting) restart() } private fun restart() { - RESTARTING = false + restarting = false val broadcastIntent = Intent("com.tutpro.baresip.Restart") sendBroadcast(broadcastIntent) } @@ -445,16 +631,27 @@ class BaresipService: Service() { companion object { - var IS_SERVICE_RUNNING = false - var RESTARTING = false val STATUS_NOTIFICATION_ID = 101 val CALL_NOTIFICATION_ID = 102 val MESSAGE_NOTIFICATION_ID = 103 + val DEFAULT_CHANNEL_ID = "com.tutpro.baresip.default" val HIGH_CHANNEL_ID = "com.tutpro.baresip.high" + + var isServiceRunning = false var disconnected = false var forceStop = false + var uas = ArrayList() + var status = ArrayList() + var calls = ArrayList() + var history = ArrayList() + var messages = ArrayList() + var contacts = ArrayList() + + var audioFocused = false + var audioFocusRequest: AudioFocusRequest? = null + } init { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt index 95172400..2b3f4909 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt @@ -13,6 +13,10 @@ class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: S companion object { + fun calls(): ArrayList { + return BaresipService.calls + } + fun calls(calls: ArrayList, dir: String): ArrayList { val result = ArrayList() for (i in calls.indices) { @@ -21,17 +25,24 @@ class Call(val callp: String, val ua: UserAgent, val peerURI: String, val dir: S return result } - fun uaCalls(calls: ArrayList, ua: UserAgent, dir: String): ArrayList { + fun calls(dir: String): ArrayList { val result = ArrayList() - for (i in calls.indices) - if ((calls[i].ua == ua) && ((dir == "") || calls[i].dir == dir)) result.add(calls[i]) + for (c in BaresipService.calls) { + if (c.dir == dir) result.add(c) + } return result } - fun find(calls: ArrayList, callp: String): Call? { - for (c in calls) { + fun uaCalls(ua: UserAgent, dir: String): ArrayList { + val result = ArrayList() + for (c in BaresipService.calls) + if ((c.ua == ua) && ((dir == "") || c.dir == dir)) result.add(c) + return result + } + + fun find(callp: String): Call? { + for (c in BaresipService.calls) if (c.callp == callp) return c - } return null } diff --git a/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt b/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt index bcb3a80e..2b074342 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt @@ -1,6 +1,8 @@ package com.tutpro.baresip -import java.io.Serializable +import android.util.Log +import java.io.* +import java.util.ArrayList import java.util.GregorianCalendar class CallHistory(val aor: String, val peerURI: String, val direction: String, @@ -10,29 +12,72 @@ class CallHistory(val aor: String, val peerURI: String, val direction: String, companion object { - fun aorHistory(history: ArrayList, aor: String): Int { + const val CALL_HISTORY_SIZE = 100 + + fun history(): ArrayList { + return BaresipService.history + } + + fun historySet(history: ArrayList) { + BaresipService.history = history + } + + fun add(history: CallHistory) { + BaresipService.history.add(history) + if (aorHistorySize(history.aor) > CALL_HISTORY_SIZE) { + var i = 0 + for (h in BaresipService.history) + if (h.aor == history.aor) + break + else + i++ + BaresipService.history.removeAt(i) + } + } + + fun aorHistorySize(aor: String): Int { var size = 0; - for (h in history) { + for (h in BaresipService.history) { if (h.aor == aor) size++ } return size } - fun aorRemoveHistory(history: ArrayList, aor: String) { - for (h in history) { - if (h.aor == aor) { - history.remove(h) - return - } - } - } - - fun aorLatestHistory(history: ArrayList, aor: String): CallHistory? { - for (h in history.reversed()) + fun aorLatestHistory(aor: String): CallHistory? { + for (h in BaresipService.history.reversed()) if (h.aor == aor) return h return null } + fun save(path: String) { + val file = File(path, "history") + try { + val fos = FileOutputStream(file) + val oos = ObjectOutputStream(fos) + oos.writeObject(history()) + oos.close() + fos.close() + } catch (e: IOException) { + Log.w("Baresip", "OutputStream exception: " + e.toString()) + e.printStackTrace() + } + } + + fun restore(path: String) { + val file = File(path + "/history") + if (file.exists()) { + try { + val fis = FileInputStream(file) + val ois = ObjectInputStream(fis) + historySet(ois.readObject() as ArrayList) + ois.close() + fis.close() + } catch (e: Exception) { + Log.w("Baresip", "InputStream exception: - " + e.toString()) + } + } + + } } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt index c13e2c96..c20bd6e0 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt @@ -11,12 +11,6 @@ import android.view.MenuItem import android.widget.AdapterView import android.widget.ListView import android.widget.TextView -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.ObjectInputStream -import java.io.ObjectOutputStream import java.text.SimpleDateFormat import java.util.ArrayList @@ -120,7 +114,12 @@ class CallsActivity : AppCompatActivity() { true } - ua!!.account.missedCalls = false + ua.account.missedCalls = false + } + + override fun onPause() { + CallHistory.save(applicationContext.filesDir.absolutePath) + super.onPause() } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -137,8 +136,8 @@ class CallsActivity : AppCompatActivity() { private fun aorGenerateHistory(aor: String) { uaHistory.clear() - for (i in MainActivity.history.indices.reversed()) { - val h = MainActivity.history[i] + for (i in CallHistory.history().indices.reversed()) { + val h = CallHistory.history()[i] if (h.aor == aor) { var direction: Int if (h.direction == "in") @@ -171,7 +170,7 @@ class CallsActivity : AppCompatActivity() { private fun removeUaHistoryAt(i: Int) { for (index in uaHistory[i].indexes) - MainActivity.history.removeAt(index) + CallHistory.history().removeAt(index) uaHistory.removeAt(i) } @@ -182,38 +181,4 @@ class CallsActivity : AppCompatActivity() { now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH) } - companion object { - - fun saveHistory() { - val file = File(MainActivity.filesPath, "history") - try { - val fos = FileOutputStream(file) - val oos = ObjectOutputStream(fos) - oos.writeObject(MainActivity.history) - oos.close() - fos.close() - } catch (e: IOException) { - Log.w("Baresip", "OutputStream exception: " + e.toString()) - e.printStackTrace() - } - } - - fun restoreHistory(path: String) { - val file = File(path + "/history") - if (file.exists()) { - try { - val fis = FileInputStream(file) - val ois = ObjectInputStream(fis) - @SuppressWarnings("unchecked") - MainActivity.history = ois.readObject() as ArrayList - Log.d("Baresip", "Restored History of ${MainActivity.history.size} entries") - ois.close() - fis.close() - } catch (e: Exception) { - Log.w("Baresip", "InputStream exception: - " + e.toString()) - } - } - - } - } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt index 5dfc0f9f..65eb7d5d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt @@ -82,7 +82,7 @@ class ChatActivity : AppCompatActivity() { startActivityForResult(i, MainActivity.CONTACT_CODE) } DialogInterface.BUTTON_NEGATIVE -> { - MainActivity.messages.remove(chatMessages[pos]) + Message.messages().remove(chatMessages[pos]) chatMessages.removeAt(pos) mlAdapter.notifyDataSetChanged() if (chatMessages.size == 0) { @@ -118,10 +118,10 @@ class ChatActivity : AppCompatActivity() { imm.hideSoftInputFromWindow(newMessage.windowToken, 0) val time = System.currentTimeMillis() val msg = Message(aor, peerUri, R.drawable.arrow_up_yellow, msgText, time,true) - MainActivity.messages.add(msg) + Message.add(msg) chatMessages.add(msg) mlAdapter.notifyDataSetChanged() - if (message_send(ua!!.uap, peerUri, msgText, time.toString()) != 0) + if (Api.message_send(ua.uap, peerUri, msgText, time.toString()) != 0) Toast.makeText(getApplicationContext(), "Sending of message failed!", Toast.LENGTH_SHORT).show() else @@ -146,7 +146,7 @@ class ChatActivity : AppCompatActivity() { } override fun onPause() { - ChatsActivity.saveMessages() + ChatsActivity.saveMessages(applicationContext.filesDir.absolutePath) super.onPause() } @@ -169,7 +169,7 @@ class ChatActivity : AppCompatActivity() { Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) intent.putExtra("action", "call") intent.putExtra("uap", ua.uap) - intent.putExtra("peer", ContactsActivity.contactName(peerUri)) + intent.putExtra("peer", peerUri) startActivity(intent) } } @@ -185,7 +185,7 @@ class ChatActivity : AppCompatActivity() { private fun uaPeerMessages(aor: String, peerUri: String): ArrayList { val res = ArrayList() - for (m in MainActivity.messages) + for (m in Message.messages()) if ((m.aor == aor) && (m.peerUri == peerUri)) res.add(m) return res } @@ -203,6 +203,4 @@ class ChatActivity : AppCompatActivity() { } } - external fun message_send(uap: String, peer_uri: String, message: String, time: String): Int - } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt index ab4f0403..f5df3478 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt @@ -30,6 +30,8 @@ class ChatsActivity: AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_chats) + filesPath = applicationContext.filesDir.absolutePath + listView = findViewById(R.id.chats) as ListView plusButton = findViewById(R.id.plusButton) as ImageButton @@ -67,13 +69,13 @@ class ChatsActivity: AppCompatActivity() { DialogInterface.BUTTON_NEGATIVE -> { val peerUri = uaMessages[pos].peerUri val msgs = ArrayList() - for (m in MainActivity.messages) + for (m in Message.messages()) if ((m.aor != aor) || (m.peerUri != peerUri)) msgs.add(m) else clAdapter.remove(m) clAdapter.notifyDataSetChanged() - MainActivity.messages = msgs + Message.messages(msgs) uaMessages = uaMessages(aor) } DialogInterface.BUTTON_POSITIVE -> { @@ -84,8 +86,8 @@ class ChatsActivity: AppCompatActivity() { val builder = AlertDialog.Builder(this@ChatsActivity, R.style.Theme_AppCompat) val peer = ContactsActivity.contactName(uaMessages[pos].peerUri) if (peer.startsWith("sip:")) - builder.setMessage("Do you want to delete chat with peer '" + - "${Utils.friendlyUri(peer, Utils.aorDomain(aor))}' or add peer to contacts?") + builder.setMessage("Do you want to delete chat with peer " + + "${Utils.friendlyUri(peer, Utils.aorDomain(aor))} or add peer to contacts?") .setPositiveButton("Cancel", dialogClickListener) .setNegativeButton("Delete Chat", dialogClickListener) .setNeutralButton("Add Contact", dialogClickListener) @@ -101,7 +103,7 @@ class ChatsActivity: AppCompatActivity() { peerUri = findViewById(R.id.peerUri) as AutoCompleteTextView peerUri.threshold = 2 peerUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, - ContactsActivity.contacts.map{Contact -> Contact.name})) + Contact.contacts().map{Contact -> Contact.name})) plusButton.setOnClickListener { val uriText = peerUri.text.toString().trim() @@ -143,7 +145,7 @@ class ChatsActivity: AppCompatActivity() { } override fun onPause() { - saveMessages() + saveMessages(applicationContext.filesDir.absolutePath) super.onPause() } @@ -171,7 +173,7 @@ class ChatsActivity: AppCompatActivity() { private fun uaMessages(aor: String) : ArrayList { val res = ArrayList() - for (m in MainActivity.messages.reversed()) { + for (m in Message.messages().reversed()) { if (m.aor != aor) continue var found = false for (r in res) @@ -186,32 +188,34 @@ class ChatsActivity: AppCompatActivity() { companion object { - fun saveUaMessage(aor: String, time: Long) { - for (i in MainActivity.messages.indices.reversed()) - if ((MainActivity.messages[i].aor == aor) && - (MainActivity.messages[i].timeStamp == time)) { - MainActivity.messages[i].new = false - saveMessages() + var filesPath = "" + + fun saveUaMessage(aor: String, time: Long, path: String) { + for (i in Message.messages().indices.reversed()) + if ((Message.messages()[i].aor == aor) && + (Message.messages()[i].timeStamp == time)) { + Message.messages()[i].new = false + saveMessages(path) return } } - fun deleteUaMessage(aor: String, time: Long) { - for (i in MainActivity.messages.indices.reversed()) - if ((MainActivity.messages[i].aor == aor) && - (MainActivity.messages[i].timeStamp == time)) { - MainActivity.messages.removeAt(i) - saveMessages() + fun deleteUaMessage(aor: String, time: Long, path: String) { + for (i in Message.messages().indices.reversed()) + if ((Message.messages()[i].aor == aor) && + (Message.messages()[i].timeStamp == time)) { + Message.messages().removeAt(i) + saveMessages(path) return } } - fun saveMessages() { - val file = File(MainActivity.filesPath, "messages") + fun saveMessages(path: String = filesPath) { + val file = File(path, "messages") try { val fos = FileOutputStream(file) val oos = ObjectOutputStream(fos) - oos.writeObject(MainActivity.messages) + oos.writeObject(Message.messages()) oos.close() fos.close() } catch (e: IOException) { @@ -220,15 +224,13 @@ class ChatsActivity: AppCompatActivity() { } } - fun restoreMessages(path: String) { + fun restoreMessages(path: String = filesPath) { val file = File(path, "messages") if (file.exists()) { try { val fis = FileInputStream(file) val ois = ObjectInputStream(fis) - @SuppressWarnings("unchecked") - MainActivity.messages = ois.readObject() as ArrayList - Log.d("Baresip", "Restored ${MainActivity.messages.size} messages") + Message.messages(ois.readObject() as ArrayList) ois.close() fis.close() } catch (e: Exception) { @@ -236,5 +238,6 @@ class ChatsActivity: AppCompatActivity() { } } } + } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index c18f66f8..d8ee917b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -1,3 +1,16 @@ package com.tutpro.baresip -class Contact(var name: String, var uri: String) \ No newline at end of file +import java.util.ArrayList + +class Contact(var name: String, var uri: String) { + + companion object { + + const val CONTACTS_SIZE = 100 + + fun contacts(): ArrayList { + return BaresipService.contacts + } + + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt index d1dd50c4..7e1fc480 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt @@ -37,10 +37,10 @@ class ContactActivity : AppCompatActivity() { } } else { index = intent.extras.getInt("index") - val name = ContactsActivity.contacts[index].name + val name = Contact.contacts()[index].name setTitle(name) nameView.setText(name) - uriView.setText(ContactsActivity.contacts[index].uri) + uriView.setText(Contact.contacts()[index].uri) } } @@ -63,7 +63,7 @@ class ContactActivity : AppCompatActivity() { "Invalid contact name: $newName") return false } - if ((new || (ContactsActivity.contacts[index].name != newName)) && + if ((new || (Contact.contacts()[index].name != newName)) && ContactsActivity.nameExists(newName)) { Utils.alertView(this, "Notice", "Contact $newName already exists") @@ -73,20 +73,27 @@ class ContactActivity : AppCompatActivity() { var newUri = uriView.text.toString().trim() if (!newUri.startsWith("<")) { if (!newUri.startsWith("sip:")) newUri = "sip:$newUri" - if (!Utils.checkSipUri(newUri)) { + if (!Utils.checkAorUri(newUri)) { Utils.alertView(this, "Notice","Invalid contact URI: $newUri") return false } } if (new) { - ContactsActivity.contacts.add(Contact(newName, newUri)) + if (Contact.contacts().size >= Contact.CONTACTS_SIZE) { + Utils.alertView(this, + "Notice", "Maximum number of contacts exceeded") + return true + } else { + Contact.contacts().add(Contact(newName, newUri)) + } } else { - ContactsActivity.contacts[index].uri = newUri - ContactsActivity.contacts[index].name = newName + Contact.contacts()[index].uri = newUri + Contact.contacts()[index].name = newName } - ContactsActivity.contacts.sortBy { Contact -> Contact.name } - ContactsActivity.saveContacts() + + Contact.contacts().sortBy { Contact -> Contact.name } + ContactsActivity.saveContacts(applicationContext.filesDir.absolutePath) i.putExtra("name", newName) setResult(Activity.RESULT_OK, i) @@ -104,8 +111,10 @@ class ContactActivity : AppCompatActivity() { } companion object { + internal var index = 0 internal var new = false + } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt index 60c4f91e..025fa550 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt @@ -45,7 +45,7 @@ class ContactListAdapter(private val cxt: Context, private val rows: ArrayList when (which) { DialogInterface.BUTTON_NEGATIVE -> { - ContactsActivity.contacts.removeAt(pos) + Contact.contacts().removeAt(pos) ContactsActivity.saveContacts() this.notifyDataSetChanged() } @@ -76,7 +76,7 @@ class ContactListAdapter(private val cxt: Context, private val rows: ArrayList= Contact.CONTACTS_SIZE) { + Utils.alertView(this, "Notice", + "Your maximum number of contacts " + + "(${Contact.CONTACTS_SIZE}) has been exceeded.") + } else { + val i = Intent(this, ContactActivity::class.java) + val b = Bundle() + b.putBoolean("new", true) + b.putString("uri", "") + i.putExtras(b) + startActivityForResult(i, MainActivity.CONTACT_CODE) + } } } @@ -64,34 +70,32 @@ class ContactsActivity : AppCompatActivity() { companion object { - var contacts = ArrayList() + internal var filesPath = "" - fun saveContacts() { + fun saveContacts(path: String = filesPath) { var contents = "" - for (c in contacts) + for (c in Contact.contacts()) contents += "\"${c.name}\" ${c.uri}\n" - val path = MainActivity.filesPath + "/contacts" - Utils.putFileContents(File(path), contents) - Log.d("Baresip", "Saved contacts '${contents}' to '$path") + Utils.putFileContents(File(path + "/contacts"), contents) } - fun restoreContacts(path: String) { + fun restoreContacts(path: String = filesPath) { val content = Utils.getFileContents(File(path + "/contacts")) Api.contacts_remove() - contacts.clear() + Contact.contacts().clear() content.lines().forEach { val parts = it.split("\"") if (parts.size == 3) { val name = parts[1] val uri = parts[2].trim() Api.contact_add("\"$name\" $uri") - contacts.add(Contact(name, uri)) + Contact.contacts().add(Contact(name, uri)) } } } fun findContactURI(name: String): String { - for (c in contacts) + for (c in Contact.contacts()) if (c.name == name) return c.uri.removePrefix("<") .replaceAfter(">", "") @@ -100,13 +104,13 @@ class ContactsActivity : AppCompatActivity() { } fun nameExists(name: String): Boolean { - for (c in contacts) + for (c in Contact.contacts()) if (c.name.equals(name, ignoreCase = true)) return true return false } fun contactName(uri: String): String { - for (c in contacts) + for (c in Contact.contacts()) if ((Utils.uriUserPart(c.uri) == Utils.uriUserPart(uri)) && (Utils.uriHostPart(c.uri) == Utils.uriHostPart(uri))) return c.name diff --git a/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt index 0571f74f..4ca6b3ca 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/EditConfigActivity.kt @@ -7,10 +7,10 @@ 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 @@ -27,8 +27,7 @@ class EditConfigActivity : AppCompatActivity() { setContentView(R.layout.activity_edit_config) editText = findViewById(R.id.editConfig) as EditText - path = applicationContext.filesDir.absolutePath + "/config" - // path = "/sdcard/baresip/config"; + path = "${applicationContext.filesDir.absolutePath}/config" file = File(path!!) var content: String diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 585b2d99..47289910 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.annotation.TargetApi import android.app.NotificationManager import android.content.* import android.support.v4.app.ActivityCompat @@ -15,22 +14,18 @@ import android.view.MenuItem import android.content.pm.PackageManager import android.graphics.Color import android.media.* -import android.os.Build import android.support.v4.content.LocalBroadcastManager import android.support.v7.view.menu.ActionMenuItemView import android.view.inputmethod.InputMethodManager -import android.text.Editable import android.text.InputType import android.text.TextWatcher import android.widget.RelativeLayout import android.widget.* import android.view.* -import java.util.* import kotlin.collections.ArrayList class MainActivity : AppCompatActivity() { - lateinit var appContext: Context internal lateinit var layout: RelativeLayout internal lateinit var baresipService: Intent internal lateinit var callTitle: TextView @@ -51,7 +46,6 @@ class MainActivity : AppCompatActivity() { internal lateinit var aorSpinner: Spinner internal lateinit var am: AudioManager internal lateinit var rt: Ringtone - internal var rtTimer: Timer? = null internal lateinit var imm: InputMethodManager internal lateinit var nm: NotificationManager internal lateinit var serviceEventReceiver: BroadcastReceiver @@ -69,9 +63,15 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) - filesPath = applicationContext.filesDir.absolutePath + if (ContextCompat.checkSelfPermission(applicationContext, + Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED) { + Toast.makeText(applicationContext,"You have not granted microphone permission.", + Toast.LENGTH_SHORT).show() + finishAndRemoveTask() + System.exit(0) + return + } - appContext = applicationContext layout = findViewById(R.id.mainActivityLayout) as RelativeLayout callTitle = findViewById(R.id.callTitle) as TextView callUri = findViewById(R.id.callUri) as AutoCompleteTextView @@ -112,16 +112,16 @@ class MainActivity : AppCompatActivity() { } aorSpinner = findViewById(R.id.AoRList) - uaAdapter = UaSpinnerAdapter(applicationContext, uas, images) + uaAdapter = UaSpinnerAdapter(applicationContext, UserAgent.uas(), UserAgent.status()) aorSpinner.adapter = uaAdapter aorSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { Log.d("Baresip", "aorSpinner selecting $position") - val acc = uas[position].account + val acc = UserAgent.uas()[position].account val aor = acc.aor - val ua = uas[position] + val ua = UserAgent.uas()[position] Log.d("Baresip", "Setting $aor current") - uag_current_set(uas[position].uap) + Api.uag_current_set(UserAgent.uas()[position].uap) showCall(ua) if (acc.vmUri != "") { if (acc.vmNew > 0) @@ -151,11 +151,12 @@ class MainActivity : AppCompatActivity() { true } else { if ((event.x - view.left) < 100) { - val i = Intent(this, AccountActivity::class.java) + val i = Intent(this, AccountsActivity::class.java) val b = Bundle() - b.putString("accp", uas[aorSpinner.selectedItemPosition].account.accp) + b.putString("accp", + UserAgent.uas()[aorSpinner.selectedItemPosition].account.accp) i.putExtras(b) - startActivityForResult(i, ACCOUNT_CODE) + startActivityForResult(i, ACCOUNTS_CODE) true } else { false @@ -165,11 +166,12 @@ class MainActivity : AppCompatActivity() { false } } - - callUri.threshold = 2 - ContactsActivity.restoreContacts(applicationContext.filesDir.path) callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, - ContactsActivity.contacts.map{Contact -> Contact.name})) + Contact.contacts().map{Contact -> Contact.name})) + callUri.threshold = 2 + ContactsActivity.restoreContacts(applicationContext.filesDir.absolutePath) + callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, + Contact.contacts().map{Contact -> Contact.name})) securityButton.setOnClickListener { when (securityButton.tag) { @@ -186,7 +188,8 @@ 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 calls = Call.uaCalls(calls, uas[aorSpinner.selectedItemPosition], "") + val calls = Call.uaCalls(UserAgent.uas()[aorSpinner.selectedItemPosition], + "") if (calls.size > 0) { if (Api.cmd_exec("zrtp_unverify " + calls[0].zid) != 0) { Log.w("Baresip", @@ -207,9 +210,10 @@ class MainActivity : AppCompatActivity() { } callButton.setOnClickListener { - val ua = uas[aorSpinner.selectedItemPosition] + callUri.setAdapter(null) + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val aor = ua.account.aor - if (!ONE_CALL_ONLY || calls.isEmpty()) { + if (Call.calls().isEmpty()) { val uriText = callUri.text.toString().trim() if (uriText.length > 0) { var uri = ContactsActivity.findContactURI(uriText) @@ -225,66 +229,66 @@ class MainActivity : AppCompatActivity() { else call(ua, uri) } else { - val latest = CallHistory.aorLatestHistory(history, aor) + val latest = CallHistory.aorLatestHistory(aor) if (latest != null) - if (Utils.uriHostPart(latest.peerURI) == Utils.uriHostPart(aor)) - callUri.setText(Utils.uriUserPart(latest.peerURI)) - else - callUri.setText(latest.peerURI) + callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(latest.peerURI), + Utils.aorDomain(ua.account.aor))) } } } hangupButton.setOnClickListener { - val ua = uas[aorSpinner.selectedItemPosition] + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val aor = ua.account.aor - val uaCalls = Call.uaCalls(calls, ua, "") + val uaCalls = Call.uaCalls(ua, "") if (uaCalls.size > 0) { val callp = uaCalls[uaCalls.size - 1].callp Log.d("Baresip", "AoR $aor hanging up call $callp with ${callUri.text}") hangupButton.isEnabled = false - ua_hangup(ua.uap, callp, 0, "") + Api.ua_hangup(ua.uap, callp, 0, "") } } answerButton.setOnClickListener { - val ua = uas[aorSpinner.selectedItemPosition] + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val aor = ua.account.aor - val callp = Call.uaCalls(calls, ua,"in")[0].callp + val callp = Call.uaCalls(ua,"in")[0].callp Log.d("Baresip", "AoR $aor answering call $callp from ${callUri.text}") answerButton.isEnabled = false - ua_answer(ua.uap, callp) + Api.ua_answer(ua.uap, callp) } rejectButton.setOnClickListener { - val ua = uas[aorSpinner.selectedItemPosition] + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val aor = ua.account.aor - val callp = Call.uaCalls(calls, ua,"in")[0].callp + val callp = Call.uaCalls(ua,"in")[0].callp Log.d("Baresip", "AoR $aor rejecting call $callp from ${callUri.text}") rejectButton.isEnabled = false - ua_hangup(ua.uap, callp, 486, "Rejected") + Api.ua_hangup(ua.uap, callp, 486, "Rejected") } holdButton.setOnClickListener { - val ua = uas[aorSpinner.selectedItemPosition] + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val aor = ua.account.aor - val call = Call.uaCalls(calls, ua,"")[0] + val call = Call.uaCalls(ua,"")[0] if (call.onhold) { Log.d("Baresip", "AoR $aor resuming call ${call.callp} with ${callUri.text}") - call_unhold(call.callp) + Api.call_unhold(call.callp) call.onhold = false holdButton.setImageResource(R.drawable.pause) } else { Log.d("Baresip", "AoR $aor holding call ${call.callp} with ${callUri.text}") - call_hold(call.callp) + Api.call_hold(call.callp) call.onhold = true holdButton.setImageResource(R.drawable.play) } } + dtmf.tag = ArrayList() + voicemailButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { - val ua = uas[aorSpinner.selectedItemPosition] + val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val acc = ua.account if (acc.vmUri != "") { val dialogClickListener = DialogInterface.OnClickListener { _, which -> @@ -317,19 +321,19 @@ class MainActivity : AppCompatActivity() { val i = Intent(this@MainActivity, ContactsActivity::class.java) val b = Bundle() if (aorSpinner.selectedItemPosition >= 0) - b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor) + b.putString("aor", UserAgent.uas()[aorSpinner.selectedItemPosition].account.aor) else b.putString("aor", "") i.putExtras(b) startActivityForResult(i, CONTACTS_CODE) } - ChatsActivity.restoreMessages(applicationContext.filesDir.path) + ChatsActivity.restoreMessages(applicationContext.filesDir.absolutePath) messagesButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, ChatsActivity::class.java) val b = Bundle() - b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor) + b.putString("aor", UserAgent.uas()[aorSpinner.selectedItemPosition].account.aor) b.putString("peer", "") b.putBoolean("focus", false) i.putExtras(b) @@ -337,12 +341,12 @@ class MainActivity : AppCompatActivity() { } } - CallsActivity.restoreHistory(applicationContext.filesDir.path) + CallHistory.restore(applicationContext.filesDir.absolutePath) callsButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, CallsActivity::class.java) val b = Bundle() - b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor) + b.putString("aor", UserAgent.uas()[aorSpinner.selectedItemPosition].account.aor) i.putExtras(b) startActivityForResult(i, CALLS_CODE) } @@ -363,7 +367,7 @@ class MainActivity : AppCompatActivity() { } baresipService = Intent(this@MainActivity, BaresipService::class.java) - if (!BaresipService.IS_SERVICE_RUNNING) { + if (!BaresipService.isServiceRunning) { baresipService.setAction("Start") startService(baresipService) } @@ -372,7 +376,7 @@ class MainActivity : AppCompatActivity() { moveTaskToBack(true) if (intent.hasExtra("action")) { - // MainActivity was not running when call or message came in + // MainActivity was not visible when call, message, or transfer request came in handleIntent(intent) } } @@ -385,7 +389,7 @@ class MainActivity : AppCompatActivity() { return } val uap = params[0] - val ua = UserAgent.find(uas, uap) + val ua = UserAgent.find(uap) if (ua == null) { Log.w("Baresip", "handleServiceEvent '$event' did not find ua $uap") return @@ -394,8 +398,8 @@ class MainActivity : AppCompatActivity() { Log.d("Baresip", "Handling service event '${ev[0]}' for $uap") val aor = ua.account.aor val acc = ua.account - for (account_index in uas.indices) { - if (uas[account_index].account.aor == aor) { + for (account_index in UserAgent.uas().indices) { + if (UserAgent.uas()[account_index].account.aor == aor) { when (ev[0]) { "ua added" -> { uaAdapter.notifyDataSetChanged() @@ -406,70 +410,49 @@ class MainActivity : AppCompatActivity() { } "call ringing" -> { } + "call rejected" -> { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { + callsButton.setImageResource(R.drawable.calls_missed) + } + } "call incoming" -> { val callp = params[1] - if (ContextCompat.checkSelfPermission(applicationContext, - Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_DENIED) { - Toast.makeText(applicationContext, - "You have not granted microphone permission.", - Toast.LENGTH_SHORT).show() - ua_hangup(uap, callp, 486, "Busy Here") + val call = Call.find(callp) + if (call == null) { + Log.e("Baresip", "Incoming call $callp not found") return } - val peer_uri = Api.call_peeruri(callp) - val new_call = Call(callp, ua, peer_uri, "in", "incoming", - dtmfWatcher(callp)) - if (ONE_CALL_ONLY && (calls.size > 0)) { - Log.d("Baresip", "Auto-rejecting incoming call $uap/$callp/$peer_uri") - ua_hangup(uap, callp, 486, "Busy Here") - if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE) - CallHistory.aorRemoveHistory(history, aor) - history.add(CallHistory(aor, peer_uri, "in", false)) - CallsActivity.saveHistory() - acc.missedCalls = true - if (ua == uas[aorSpinner.selectedItemPosition]) { - callsButton.setImageResource(R.drawable.calls_missed) - } + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) { + aorSpinner.setSelection(account_index) } else { - Log.d("Baresip", "Incoming call $uap/$callp/$peer_uri") - calls.add(new_call) - if (ua != uas[aorSpinner.selectedItemPosition]) { - aorSpinner.setSelection(account_index) - } else { - callTitle.text = "Incoming call from ..." - callUri.setText(new_call.peerURI) - securityButton.visibility = View.INVISIBLE - callButton.visibility = View.INVISIBLE - hangupButton.visibility = View.INVISIBLE - answerButton.visibility = View.VISIBLE - answerButton.isEnabled = true - rejectButton.visibility = View.VISIBLE - rejectButton.isEnabled = true - holdButton.visibility = View.INVISIBLE - dtmf.visibility = View.INVISIBLE - } - startRinging() - if (Utils.isVisible()) { + callTitle.text = "Incoming call from ..." + callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.peerURI), + Utils.aorDomain(ua.account.aor))) + securityButton.visibility = View.INVISIBLE + callButton.visibility = View.INVISIBLE + hangupButton.visibility = View.INVISIBLE + answerButton.visibility = View.VISIBLE + answerButton.isEnabled = true + rejectButton.visibility = View.VISIBLE + rejectButton.isEnabled = true + holdButton.visibility = View.INVISIBLE + dtmf.visibility = View.INVISIBLE + } + if (Utils.isVisible()) { Log.d("Baresip", "Baresip is visible") val i = Intent(applicationContext, MainActivity::class.java) i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) startActivity(i) - } } } "call established" -> { val callp = params[1] - val call = Call.find(calls, callp) + val call = Call.find(callp) if (call == null) { Log.e("Baresip", "Established call $callp not found") return } - Log.d("Baresip", "AoR $aor call $callp established") - call.status = "connected" - call.onhold = false - call.security = R.drawable.box_red - if (ua == uas[aorSpinner.selectedItemPosition]) { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { if (call.dir == "in") callTitle.text = "Incoming call from ..." else @@ -492,28 +475,13 @@ class MainActivity : AppCompatActivity() { dtmf.hint = "DTMF" dtmf.visibility = View.VISIBLE dtmf.requestFocus() - dtmf.addTextChangedListener(call.dtmfWatcher) } - if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE) - CallHistory.aorRemoveHistory(history, aor) - history.add(CallHistory(aor, call.peerURI, call.dir, true)) - CallsActivity.saveHistory() - call.hasHistory = true - if (rtTimer != null) { - rtTimer!!.cancel() - rtTimer = null - if (rt.isPlaying) rt.stop() - } - am.mode = AudioManager.MODE_IN_COMMUNICATION - requestAudioFocus( AudioManager.STREAM_VOICE_CALL) - am.isSpeakerphoneOn = false - // am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, - // (am.getStreamMaxVolume(STREAM_VOICE_CALL) / 2) + 1, - // AudioManager.STREAM_VOICE_CALL) + (dtmf.tag as ArrayList).add(call.dtmfWatcher!!) + dtmf.addTextChangedListener(call.dtmfWatcher) } "call verify" -> { val callp = params[1] - val call = Call.find(calls, callp) + val call = Call.find(callp) if (call == null) { Log.e("Baresip", "Call $callp to be verified is not found") return @@ -531,7 +499,7 @@ class MainActivity : AppCompatActivity() { } call.security = security call.zid = ev[3] - if (ua == uas[aorSpinner.selectedItemPosition]) { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { securityButton.setImageResource(security) setSecurityButtonTag(securityButton, security) securityButton.visibility = View.VISIBLE @@ -541,7 +509,7 @@ class MainActivity : AppCompatActivity() { verifyDialog.setNegativeButton("No") { dialog, _ -> call.security = R.drawable.box_yellow call.zid = ev[3] - if (ua == uas[aorSpinner.selectedItemPosition]) { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { securityButton.setImageResource(R.drawable.box_yellow) securityButton.tag = "yellow" securityButton.visibility = View.VISIBLE @@ -552,7 +520,7 @@ class MainActivity : AppCompatActivity() { } "call verified", "call secure" -> { val callp = params[1] - val call = Call.find(calls, callp) + val call = Call.find(callp) if (call == null) { Log.e("Baresip", "Call $callp that is verified is not found") return @@ -566,7 +534,7 @@ class MainActivity : AppCompatActivity() { tag = "green" call.zid = ev[1] } - if (ua == uas[aorSpinner.selectedItemPosition]) { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { securityButton.setImageResource(call.security) securityButton.tag = tag securityButton.visibility = View.VISIBLE @@ -574,86 +542,53 @@ class MainActivity : AppCompatActivity() { } "call transfer" -> { val callp = params[1] - val call = Call.find(calls, callp) + val call = Call.find(callp) if (call == null) { - Log.e("Baresip", "Call $callp to be transfered is not found") + Log.e("Baresip", "Call $callp to be transferred is not found") return } val transferDialog = AlertDialog.Builder(this) - transferDialog.setMessage("Do you want to transfer this call to ${ev[1]}?") + val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]), + Utils.aorDomain(aor)) + transferDialog.setMessage("Do you accept to transfer call to $target?") transferDialog.setPositiveButton("Yes") { dialog, _ -> - if (call in calls) + if (call in Call.calls()) transfer(ua, call, ev[1]) else call(ua, ev[1]) dialog.dismiss() } transferDialog.setNegativeButton("No") { dialog, _ -> - if (call in calls) - call_notify_sipfrag(callp, 603, "Decline") + if (call in Call.calls()) + Api.call_notify_sipfrag(callp, 603, "Decline") dialog.dismiss() } transferDialog.create().show() } - "transfer failed" -> { - val callp = params[1] - Log.d("Baresip", "AoR $aor hanging up call $callp with ${ev[1]}") - ua_hangup(ua.uap, callp, 0, "") - } "call closed" -> { - val callp = params[1] - val call = Call.find(calls, callp) - if (call == null) { - Log.d("Baresip", "Call $callp that is closed is not found") - return + val watchers = dtmf.tag as ArrayList + if (watchers.size > 0) { + dtmf.removeTextChangedListener(watchers[0]) + watchers.removeAt(0) } - Log.d("Baresip", "Removing call ${uap}/${callp}/" + call.peerURI) - stopRinging() - if (ua == uas[aorSpinner.selectedItemPosition]) { - dtmf.removeTextChangedListener(call.dtmfWatcher) - } - calls.remove(call) - if (ua == uas[aorSpinner.selectedItemPosition]) { + if (ua == UserAgent.uas()[aorSpinner.selectedItemPosition]) { showCall(ua) + if (ua.account.missedCalls) + callsButton.setImageResource(R.drawable.calls_missed) } - if (!call.hasHistory) { - if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE) - CallHistory.aorRemoveHistory(history, aor) - history.add(CallHistory(aor, call.peerURI, call.dir,false)) - CallsActivity.saveHistory() - if (call.dir == "in") { - acc.missedCalls = true - if (ua == uas[aorSpinner.selectedItemPosition]) - callsButton.setImageResource(R.drawable.calls_missed) - } - } - if (calls.size == 0) { - am.mode = AudioManager.MODE_NORMAL - } else { - val uaCalls = Call.uaCalls(calls, ua, "") - if (uaCalls.size > 0) call_start_audio(uaCalls[uaCalls.size - 1].callp) - } - if (am.isSpeakerphoneOn) { - am.isSpeakerphoneOn = false - val speakerIcon = findViewById(R.id.speakerIcon) as ActionMenuItemView - speakerIcon.setBackgroundColor(ContextCompat.getColor(applicationContext, + val speakerIcon = findViewById(R.id.speakerIcon) as ActionMenuItemView + speakerIcon.setBackgroundColor(ContextCompat.getColor(applicationContext, R.color.colorPrimary)) - } - if (audioFocused) { - abandonAudioFocus() - } } "message" -> { val peerUri = params[1] val msgText = params[2] val time = params[3] - val newMsg = Message(aor, peerUri, R.drawable.arrow_down_green, msgText, - time.toLong(), true) Log.d("Baresip", "Incoming message $aor/$peerUri/$msgText") - messages.add(newMsg) - ChatsActivity.saveMessages() + Message.add(Message(aor, peerUri, R.drawable.arrow_down_green, msgText, + time.toLong(), true)) if (Utils.isVisible()) { - if (ua != uas[aorSpinner.selectedItemPosition]) + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) aorSpinner.setSelection(account_index) val i = Intent(applicationContext, ChatsActivity::class.java) i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -676,7 +611,7 @@ class MainActivity : AppCompatActivity() { } } if ((aorSpinner.selectedItemPosition >= 0) && - (ua == uas[aorSpinner.selectedItemPosition])) + (ua == UserAgent.uas()[aorSpinner.selectedItemPosition])) if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) else @@ -702,26 +637,19 @@ class MainActivity : AppCompatActivity() { Log.d("Baresip", "Handling intent '$action'") when (action) { "call" -> { - if (!calls.isEmpty()) { + if (!Call.calls().isEmpty()) { Toast.makeText(applicationContext, "You already have an active call!", Toast.LENGTH_SHORT).show() return } val uap = intent.getStringExtra("uap") - val ua = UserAgent.find(MainActivity.uas, uap) + val ua = UserAgent.find(uap) if (ua == null) { - Log.e("Baresip", "onNewIntent did not find ua $uap") + Log.e("Baresip", "handleIntent 'call' did not find ua $uap") return } - val aor = ua.account.aor - if (ua != uas[aorSpinner.selectedItemPosition]) { - for (account_index in uas.indices) { - if (uas[account_index].account.aor == aor) { - aorSpinner.setSelection(account_index) - break - } - } - } + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) makeCall = intent.getStringExtra("peer") } "answer" -> { @@ -731,9 +659,33 @@ class MainActivity : AppCompatActivity() { rejectCall = intent.getStringExtra("callp") moveTaskToBack(true) } + "transfer" -> { + val callp = intent.getStringExtra("callp") + val call = Call.find(callp) + val uap = intent.getStringExtra("uap") + val ua = UserAgent.find(uap) + if (ua == null) { + Log.e("Baresip", "Ua $uap of call transfer target does not exist") + moveTaskToBack(true) + return + } + if (call == null) + call(ua, intent.getStringExtra("uri")) + else + transfer(ua, call, intent.getStringExtra("uri")) + } + "deny" -> { + val callp = intent.getStringExtra("callp") + val call = Call.find(callp) + if (call == null) + Log.d("Baresip", "Call $callp denied transfer does not exist anymore") + else + Api.call_notify_sipfrag(callp, 603, "Decline") + moveTaskToBack(true) + } "reply", "save", "delete" -> { val uap = intent.getStringExtra("uap") - val ua = UserAgent.find(MainActivity.uas, uap) + val ua = UserAgent.find(uap) if (ua == null) { Log.e("Baresip", "onNewIntent did not find ua $uap") return @@ -743,14 +695,8 @@ class MainActivity : AppCompatActivity() { "reply" -> { val i = Intent(this@MainActivity, ChatsActivity::class.java) val b = Bundle() - if (ua != uas[aorSpinner.selectedItemPosition]) { - for (account_index in uas.indices) { - if (uas[account_index].account.aor == aor) { - aorSpinner.setSelection(account_index) - break - } - } - } + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(aor) b.putString("aor", aor) b.putString("peer", intent.getStringExtra("peer")) b.putBoolean("focus", true) @@ -759,19 +705,21 @@ class MainActivity : AppCompatActivity() { } "save" -> { ChatsActivity.saveUaMessage(ua.account.aor, - intent.getStringExtra("time").toLong()) + intent.getStringExtra("time").toLong(), + applicationContext.filesDir.absolutePath) moveTaskToBack(true) } "delete" -> { ChatsActivity.deleteUaMessage(ua.account.aor, - intent.getStringExtra("time").toLong()) + intent.getStringExtra("time").toLong(), + applicationContext.filesDir.absolutePath) moveTaskToBack(true) } } nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID) } "message" -> { - val ua = UserAgent.find(MainActivity.uas, intent.getStringExtra("uap"))!! + val ua = UserAgent.find(intent.getStringExtra("uap"))!! val i = Intent(applicationContext, ChatsActivity::class.java) val b = Bundle() b.putString("aor", ua.account.aor) @@ -816,7 +764,7 @@ class MainActivity : AppCompatActivity() { override fun onDestroy() { Log.d("Baresip", "Destroyed") LocalBroadcastManager.getInstance(this).unregisterReceiver(serviceEventReceiver) - if (BaresipService.IS_SERVICE_RUNNING) { + if (BaresipService.isServiceRunning) { baresipService.setAction("Kill") startService(baresipService) } @@ -844,6 +792,9 @@ class MainActivity : AppCompatActivity() { } R.id.accounts -> { i = Intent(this, AccountsActivity::class.java) + val b = Bundle() + b.putString("accp", "") + i.putExtras(b) startActivityForResult(i, ACCOUNTS_CODE) return true } @@ -860,7 +811,7 @@ class MainActivity : AppCompatActivity() { } R.id.quit -> { Log.d("Baresip", "Quiting") - if (BaresipService.IS_SERVICE_RUNNING) { + if (BaresipService.isServiceRunning) { baresipService.setAction("Stop"); startService(baresipService) } @@ -876,11 +827,11 @@ class MainActivity : AppCompatActivity() { ACCOUNTS_CODE -> { uaAdapter.notifyDataSetChanged() - if (uas.size > 0) { + if (UserAgent.uas().size > 0) { if ((aorSpinner.selectedItemPosition == -1) || - (aorSpinner.selectedItemPosition >= uas.size)) + (aorSpinner.selectedItemPosition >= UserAgent.uas().size)) aorSpinner.setSelection(0) - val acc = uas[aorSpinner.selectedItemPosition].account + val acc = UserAgent.uas()[aorSpinner.selectedItemPosition].account if (acc.vmUri != "") { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) @@ -898,7 +849,7 @@ class MainActivity : AppCompatActivity() { } ACCOUNT_CODE -> { - val acc = uas[aorSpinner.selectedItemPosition].account + val acc = UserAgent.uas()[aorSpinner.selectedItemPosition].account if (acc.vmUri != "") { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) @@ -912,14 +863,14 @@ class MainActivity : AppCompatActivity() { CONTACTS_CODE -> { callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, - ContactsActivity.contacts.map{Contact -> Contact.name})) + Contact.contacts().map{Contact -> Contact.name})) } EDIT_CONFIG_CODE -> { if (resultCode == RESULT_OK) { Utils.alertView(this, "Notice", "You need to restart baresip in order to activate saved config!") - reload_config() + Api.reload_config() } if (resultCode == RESULT_CANCELED) { Log.d("Baresip", "Edit config canceled") @@ -927,14 +878,17 @@ class MainActivity : AppCompatActivity() { } CALLS_CODE -> { - val acc = uas[aorSpinner.selectedItemPosition].account + val acc = UserAgent.uas()[aorSpinner.selectedItemPosition].account if (resultCode == RESULT_OK) { - if (data != null) - callUri.setText(data.getStringExtra("peer_uri")) + if (data != null) { + val peerUri = data.getStringExtra("peer_uri") + callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(peerUri), + Utils.aorDomain(Utils.aorDomain(acc.aor)))) + } } if (resultCode == RESULT_CANCELED) { Log.d("Baresip", "History canceled") - if (CallHistory.aorHistory(history, acc.aor) == 0) { + if (CallHistory.aorHistorySize(acc.aor) == 0) { holdButton.visibility = View.INVISIBLE } } @@ -946,6 +900,14 @@ class MainActivity : AppCompatActivity() { } } + private fun spinToAor(aor: String) { + for (account_index in UserAgent.uas().indices) + if (UserAgent.uas()[account_index].account.aor == aor) { + aorSpinner.setSelection(account_index) + break + } + } + private fun call(ua: UserAgent, uri: String) { if (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED) { @@ -953,12 +915,16 @@ class MainActivity : AppCompatActivity() { "You have not granted microphone permission.", Toast.LENGTH_SHORT).show() return } - callUri.setText(uri) + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) + callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(uri), + Utils.aorDomain(Utils.aorDomain(ua.account.aor)))) callButton.visibility = View.INVISIBLE - val callp = ua_connect(ua.uap, uri) + val callp = Api.ua_connect(ua.uap, uri) if (callp != "") { Log.d("Baresip", "Adding outgoing call ${ua.uap}/$callp/$uri") - calls.add(Call(callp, ua, uri, "out", "outgoing", dtmfWatcher(callp))) + Call.calls().add(Call(callp, ua, uri, "out", "outgoing", + Utils.dtmfWatcher(callp))) imm.hideSoftInputFromWindow(callUri.windowToken, 0) securityButton.visibility = View.INVISIBLE callButton.visibility = View.INVISIBLE @@ -975,44 +941,27 @@ class MainActivity : AppCompatActivity() { } private fun transfer(ua: UserAgent, call: Call, uri: String) { - val newCallp = ua_call_alloc(ua.uap, call.callp) + val newCallp = Api.ua_call_alloc(ua.uap, call.callp) if (newCallp != "") { Log.d("Baresip", "Adding outgoing call ${ua.uap}/$newCallp/$uri") val newCall = Call(newCallp, ua, uri, "out", "transferring", - dtmfWatcher(newCallp)) - calls.add(newCall) - call_stop_audio(call.callp) - call_start_audio(newCallp) - val err = call_connect(newCallp, uri) + Utils.dtmfWatcher(newCallp)) + Call.calls().add(newCall) + Api.call_stop_audio(call.callp) + Api.call_start_audio(newCallp) + val err = Api.call_connect(newCallp, uri) if (err == 0) { - if (ua == uas[aorSpinner.selectedItemPosition]) showCall(ua) + if (ua != UserAgent.uas()[aorSpinner.selectedItemPosition]) + spinToAor(ua.account.aor) + showCall(ua) } else { - call_start_audio(call.callp) + Api.call_start_audio(call.callp) Log.w("Baresip", "call_connect $newCallp failed with error $err") - call_notify_sipfrag(call.callp, 500, "Call Error") + Api.call_notify_sipfrag(call.callp, 500, "Call Error") } } else { Log.w("Baresip", "ua_call_alloc ${ua.uap}/${call.callp} failed") - call_notify_sipfrag(call.callp, 500, "Call Error") - } - } - - private fun dtmfWatcher(callp: String): TextWatcher { - return object : TextWatcher { - override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {} - override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) { - val text = sequence.subSequence(start, start + count).toString() - if (text.length > 0) { - val digit = text[0] - Log.d("Baresip", "Got DTMF digit '$digit'") - if (((digit >= '0') && (digit <= '9')) || (digit == '*') || (digit == '#')) - call_send_digit(callp, digit) - } - } - override fun afterTextChanged(sequence: Editable) { - // KEYCODE_REL - // call_send_digit(callp, 4.toChar()) - } + Api.call_notify_sipfrag(call.callp, 500, "Call Error") } } @@ -1031,10 +980,13 @@ class MainActivity : AppCompatActivity() { } private fun showCall(ua: UserAgent) { - if (Call.uaCalls(calls, ua, "").size == 0) { + if (Call.uaCalls(ua, "").size == 0) { callTitle.text = "Outgoing call to ..." callUri.text.clear() callUri.hint = "Callee" + callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, + Contact.contacts().map{Contact -> Contact.name})) + imm.hideSoftInputFromWindow(callUri.windowToken, 0) securityButton.visibility = View.INVISIBLE callButton.visibility = View.VISIBLE hangupButton.visibility = View.INVISIBLE @@ -1043,8 +995,8 @@ class MainActivity : AppCompatActivity() { holdButton.visibility = View.INVISIBLE dtmf.visibility = View.INVISIBLE } else { - val callsOut = Call.uaCalls(calls, ua, "out") - val callsIn = Call.uaCalls(calls, ua, "in") + val callsOut = Call.uaCalls(ua, "out") + val callsIn = Call.uaCalls(ua, "in") val call: Call if (callsOut.size > 0) { call = callsOut[callsOut.size - 1] @@ -1056,7 +1008,8 @@ class MainActivity : AppCompatActivity() { callTitle.text = "Incoming call from ..." call = callsIn[callsIn.size - 1] } - callUri.setText(call.peerURI) + callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.peerURI), + Utils.aorDomain(ua.account.aor))) when (call.status) { "outgoing", "transferring" -> { securityButton.visibility = View.INVISIBLE @@ -1112,97 +1065,8 @@ class MainActivity : AppCompatActivity() { } } - private fun requestAudioFocus(streamType: Int) { - val res: Int - if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && (audioFocusRequest == null)) { - val playbackAttributes = AudioAttributes.Builder() - .setLegacyStreamType(streamType) - .build() - @TargetApi(26) - audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) - .setAudioAttributes(playbackAttributes) - .build() - @TargetApi(26) - res = am.requestAudioFocus(audioFocusRequest) - if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - Log.d("Baresip", "Audio focus granted") - } else { - Log.d("Baresip", "Audio focus denied") - audioFocusRequest = null - } - } else { - res = am.requestAudioFocus(null, streamType, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) - if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - Log.d("Baresip", "Audio focus granted") - audioFocused = true - } else { - Log.d("Baresip", "Audio focus denied") - audioFocused = false - } - } - } - - private fun abandonAudioFocus() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (audioFocusRequest != null) { - am.abandonAudioFocusRequest(audioFocusRequest) - audioFocusRequest = null - } - } else { - if (audioFocused) { - am.abandonAudioFocus(null) - audioFocused = false - } - } - } - - private fun startRinging() { - am.mode = AudioManager.MODE_RINGTONE - requestAudioFocus(AudioManager.STREAM_RING) - rt.play() - rtTimer = Timer() - rtTimer!!.scheduleAtFixedRate(object : TimerTask() { - override fun run() { - if (!rt.isPlaying()) { - rt.play() - } - } - }, 1000 * 1, 1000 * 1) - } - - private fun stopRinging() { - if (rtTimer != null) { - rtTimer!!.cancel() - rtTimer = null - if (rt.isPlaying) rt.stop() - } - } - - external fun uag_current_set(uap: String) - external fun ua_connect(uap: String, peer_uri: String): String - external fun ua_call_alloc(uap: String, xcallp: String): String - external fun ua_answer(uap: String, callp: String) - external fun ua_hangup(uap: String, callp: String, code: Int, reason: String) - external fun call_hold(callp: String): Int - external fun call_unhold(callp: String): Int - external fun call_send_digit(callp: String, digit: Char): Int - external fun call_connect(callp: String, peer_uri: String): Int - external fun call_notify_sipfrag(callp: String, code: Int, reason: String) - external fun call_start_audio(callp: String) - external fun call_stop_audio(callp: String) - external fun reload_config(): Int - companion object { - var uas = ArrayList() - internal var images = ArrayList() - var history: ArrayList = ArrayList() - internal var calls = ArrayList() - internal var messages = ArrayList() - internal var audioFocused = false - internal var audioFocusRequest: AudioFocusRequest? = null - var filesPath = "" var visible = true var makeCall = "" var answerCall = "" @@ -1217,11 +1081,7 @@ class MainActivity : AppCompatActivity() { const val CONTACT_CODE = 7 const val MESSAGES_CODE = 8 const val MESSAGE_CODE = 9 - const val RECORD_AUDIO_PERMISSION = 1 - const val HISTORY_SIZE = 100 - const val MESSAGE_HISTORY_SIZE = 100 - const val ONE_CALL_ONLY = true } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Message.kt b/app/src/main/kotlin/com/tutpro/baresip/Message.kt index 67844fb2..ca5e6a8c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Message.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Message.kt @@ -1,7 +1,38 @@ package com.tutpro.baresip -import java.io.Serializable +import java.io.* +import java.util.ArrayList class Message(val aor: String, val peerUri: String, var direction: Int, val message: String, val timeStamp: Long, var new: Boolean): Serializable { + + companion object { + + val MESSAGE_HISTORY_SIZE = 100 + + fun messages(): ArrayList { + return BaresipService.messages + } + + fun messages(messages: ArrayList) { + BaresipService.messages = messages + } + + fun add(message: Message) { + BaresipService.messages.add(message) + var count = 0 + var firstIndex = -1 + for (i in BaresipService.messages.indices) + if (BaresipService.messages[i].aor == message.aor) { + if (count == 0) firstIndex = i + count++ + if (count > MESSAGE_HISTORY_SIZE) { + break + } + } + if (count > MESSAGE_HISTORY_SIZE) + BaresipService.messages.removeAt(firstIndex) + } + + } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt index 0b75bf6c..4e207a9c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt @@ -4,37 +4,51 @@ import android.util.Log class UserAgent (val uap: String) { - val account = Account(ua_account(uap)) + val account = Account(Api.ua_account(uap)) fun register() { if (account.regint > 0) { Log.d("Baresip", "Registering ${account.aor} UA ${uap}") - if (ua_register(uap) != 0) + if (Api.ua_register(uap) != 0) Log.e("Baresip", "Registering failed") } } fun destroy() { - ua_destroy(uap) + Api.ua_destroy(uap) } companion object { - external fun ua_alloc(uri: String): String - external fun ua_update_account(ua: String): Int - external fun ua_destroy(ua: String) - external fun ua_register(ua: String): Int - external fun ua_isregistered(ua: String): Boolean - external fun ua_unregister(ua: String) + fun uas(): ArrayList { + return BaresipService.uas + } + + fun status(): ArrayList { + return BaresipService.status + } + + fun add(ua: UserAgent, status: Int) { + BaresipService.uas.add(ua) + BaresipService.status.add(status) + } + + fun remove(ua: UserAgent) { + val index = BaresipService.uas.indexOf(ua) + if (index != -1) { + BaresipService.uas[index].destroy() + BaresipService.status.removeAt(index) + } + } fun uaAlloc(uri: String): UserAgent? { - val uap = ua_alloc(uri) + val uap = Api.ua_alloc(uri) if (uap != "") return UserAgent(uap) return null } - fun find(uas: ArrayList, uap: String): UserAgent? { - for (ua in uas) { + fun find(uap: String): UserAgent? { + for (ua in BaresipService.uas) { if (ua.uap == uap) return ua } return null @@ -50,12 +64,10 @@ class UserAgent (val uap: String) { fun register(uas: ArrayList) { for (ua in uas) { if (ua.account.regint > 0) - if (ua_register(ua.uap) != 0) + if (Api.ua_register(ua.uap) != 0) Log.e("Baresip", "Failed to register ${ua.account.aor}") } } } } -external fun ua_account(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 4e6574d8..cee363ce 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -6,6 +6,8 @@ import android.support.v7.app.AlertDialog import android.util.Log import android.os.PowerManager import android.app.KeyguardManager +import android.text.Editable +import android.text.TextWatcher import java.io.* @@ -95,9 +97,13 @@ object Utils { } fun friendlyUri(uri: String, domain: String): String { - val user = uriUserPart(uri) - val host = uriHostPart(uri) - if (host == domain) return user else return "$user@$host" + if (uri.contains("@")) { + val user = uriUserPart(uri) + val host = uriHostPart(uri) + if (host == domain) return user else return "$user@$host" + } else { + return uri + } } fun aorDomain(aor: String): String { @@ -233,4 +239,23 @@ object Utils { return isLocked } + fun dtmfWatcher(callp: String): TextWatcher { + return object : TextWatcher { + override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) { + val text = sequence.subSequence(start, start + count).toString() + if (text.length > 0) { + val digit = text[0] + Log.d("Baresip", "Got DTMF digit '$digit'") + if (((digit >= '0') && (digit <= '9')) || (digit == '*') || (digit == '#')) + Api.call_send_digit(callp, digit) + } + } + override fun afterTextChanged(sequence: Editable) { + // KEYCODE_REL + // call_send_digit(callp, 4.toChar()) + } + } + } + } diff --git a/app/src/main/res/layout/call_row.xml b/app/src/main/res/layout/call_row.xml index 0384df95..f2be2800 100644 --- a/app/src/main/res/layout/call_row.xml +++ b/app/src/main/res/layout/call_row.xml @@ -26,6 +26,7 @@ android:textAlignment="gravity" android:paddingTop="5dp" android:paddingBottom="5dp" + android:paddingEnd="5dp" android:textSize="18sp" android:maxLines="1" android:text="" />