since this is a fun learning project, moved from java to kotlin for

more fun (or desperation)
This commit is contained in:
Juha Heinanen
2018-03-17 11:18:44 +13:00
parent 2a3802a951
commit afb4635af7
31 changed files with 1352 additions and 1593 deletions
@@ -0,0 +1,31 @@
package com.tutpro.baresip
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import android.widget.TextView
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
val aboutView = findViewById(R.id.aboutText) as TextView
aboutView.isEnabled = false
aboutView.text = getString(R.string.aboutText)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
}
return true
}
}
@@ -0,0 +1,10 @@
package com.tutpro.baresip
class Account(val ua: String, val aoR: String, var status: String) {
private var statusImage: Int = 0
init {
this.statusImage = 0
}
}
@@ -0,0 +1,36 @@
package com.tutpro.baresip
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import java.util.ArrayList
class AccountSpinnerAdapter(private val cxt: Context, private val aors: ArrayList<String>,
private val images: ArrayList<Int>) :
ArrayAdapter<Int>(cxt, android.R.layout.simple_spinner_item, images) {
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
private fun getImageForPosition(position: Int, parent: ViewGroup): View {
val inflater = cxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val row = inflater.inflate(R.layout.account_spinner, parent, false)
val textView = row.findViewById(R.id.spinnerText) as TextView
textView.text = aors[position]
textView.textSize = 17f
val imageView = row.findViewById(R.id.spinnerImage) as ImageView
imageView.setImageResource(images[position])
return row
}
}
@@ -0,0 +1,5 @@
package com.tutpro.baresip
class Call(val ua: String, val call: String, val peerURI: String, var status: String) {
var hold: Boolean = false
}
@@ -0,0 +1,3 @@
package com.tutpro.baresip
class Contact(val name: String, val uri: String)
@@ -0,0 +1,103 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class EditAccountsActivity : AppCompatActivity() {
private var editText: EditText? = null
private var path: String? = null
private var file: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_accounts)
editText = findViewById(R.id.editAccounts) as EditText
path = applicationContext.filesDir.absolutePath + "/accounts"
file = File(path!!)
var content: String
if (!file!!.exists()) {
Log.e("Baresip", "Failed to find accounts file")
content = "No accounts"
} else {
Log.e("Baresip", "Found accounts file")
val length = file!!.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file!!)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
content = String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read accounts file: " + e.toString())
content = "Failed to read account file"
}
}
Log.d("Baresip", "Content length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent()
when (item.itemId) {
R.id.save -> {
try {
val fOut = FileOutputStream(file!!.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
val res = editText!!.text.toString()
try {
fWriter.write(res)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to write accounts file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find accounts file: " + e.toString())
}
Log.d("Baresip", "Updated accounts file")
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
setResult(RESULT_CANCELED, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
}
@@ -0,0 +1,103 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class EditConfigActivity : AppCompatActivity() {
private var editText: EditText? = null
private var path: String? = null
private var file: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_config)
editText = findViewById(R.id.editConfig) as EditText
path = applicationContext.filesDir.absolutePath + "/config"
// path = "/sdcard/baresip/config";
file = File(path!!)
var content: String
if (!file!!.exists()) {
Log.e("Baresip", "Failed to find config file")
content = "No config"
} else {
Log.e("Baresip", "Found config file")
val length = file!!.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file!!)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
content = String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read config file: " + e.toString())
content = "Failed to read account file"
}
}
Log.d("Baresip", "Content length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent(this, MainActivity::class.java)
when (item.itemId) {
R.id.save -> {
try {
val fOut = FileOutputStream(file!!.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
val res = editText!!.text.toString()
try {
fWriter.write(res)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to write config file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find config file: " + e.toString())
}
Log.d("Baresip", "Updated config file")
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
setResult(RESULT_CANCELED, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
}
@@ -0,0 +1,113 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.util.ArrayList
class EditContactsActivity : AppCompatActivity() {
private var editText: EditText? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_contacts)
editText = findViewById(R.id.editText) as EditText
val path = applicationContext.filesDir.absolutePath + "/contacts"
val file = File(path)
val content = Utils.getFileContents(file)
Log.d("Baresip", "Contacts length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent(this, MainActivity::class.java)
when (item.itemId) {
R.id.save -> {
val path = applicationContext.filesDir.absolutePath + "/contacts"
val file = File(path)
Utils.putFileContents(file, editText!!.text.toString())
Log.d("Baresip", "Updated contacts file")
MainActivity.contacts_remove()
updateContactsAndNames(path)
// MainActivity.CalleeAdapter.notifyDataSetChanged(); does not work
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
i.putExtra("action", "cancel")
setResult(RESULT_OK, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
companion object {
var Contacts: ArrayList<Contact> = java.util.ArrayList()
var Names: ArrayList<String> = java.util.ArrayList()
fun updateContactsAndNames(path: String) {
val file = File(path)
val lines = Utils.getFileContents(file).split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var name: String
var uri: String
Contacts.clear()
Names.clear()
for (line in lines) {
line.trim { it <= ' ' }
if (line.startsWith("#") || line.length == 0) continue
val parts = line.split("\"".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (parts.size != 3) {
Log.e("Baresip", "Invalid contacts line: $line")
continue
}
name = parts[1]
if (name.length < 2) {
Log.e("Baresip", "Too short contact display name: $name")
continue
}
uri = parts[2].trim { it <= ' ' }
if (!uri.startsWith("<") || !uri.contains(">")) {
Log.e("Baresip", "Invalid contact uri: $uri")
continue
}
MainActivity.contact_add("\"$name\" $uri")
if (uri.indexOf(";access") > 0) continue
uri = uri.substring(1, uri.indexOf(">"))
Log.d("Baresip", "Adding contact name/uri: $name/$uri")
Contacts.add(Contact(name, uri))
Names.add(name)
}
}
fun findContactURI(name: String): String {
for (c in Contacts) {
if (c.name == name) return c.uri
}
return name
}
}
}
@@ -0,0 +1,11 @@
package com.tutpro.baresip
import java.io.Serializable
import java.util.GregorianCalendar
class History(val ua: String, val call: String, val aor: String, val peerURI: String,
val direction: String, val connected: Boolean) : Serializable {
val time: GregorianCalendar = GregorianCalendar()
}
@@ -0,0 +1,129 @@
package com.tutpro.baresip
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.MenuItem
import android.widget.AdapterView
import android.widget.ListView
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Calendar
import java.util.GregorianCalendar
class HistoryActivity : AppCompatActivity() {
internal var uaHistory = ArrayList<HistoryRow>()
internal var posAtHistory = ArrayList<Int>()
internal var aor: String = ""
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_history)
val listview = findViewById(R.id.history) as ListView
val b = intent.extras
aor = b.getString("aor")
generate_ua_history(aor)
val adapter = HistoryListAdapter(this, uaHistory)
listview.adapter = adapter
listview.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
val row = uaHistory[position]
val i = Intent()
i.putExtra("peer_uri", row.peerURI)
setResult(Activity.RESULT_OK, i)
finish()
}
listview.onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, pos, _ ->
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
History.removeAt(posAtHistory[pos])
generate_ua_history(aor)
if (uaHistory.size == 0) {
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
adapter.notifyDataSetChanged()
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
val builder = AlertDialog.Builder(this@HistoryActivity,
R.style.Theme_AppCompat)
builder.setMessage("Do you want to delete " +
History[pos].peerURI + "?")
.setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show()
true
}
listview.isLongClickable = true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
Log.d("Baresip", "Back array was pressed")
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
}
return true
}
private fun generate_ua_history(aor: String) {
uaHistory.clear()
posAtHistory.clear()
for (i in History.indices.reversed()) {
val h = History[i]
if (h.aor == aor) {
val time: String
if (isToday(h.time)) {
val fmt = SimpleDateFormat("HH:mm")
time = fmt.format(h.time.time)
} else {
val fmt = SimpleDateFormat("MMM dd")
time = fmt.format(h.time.time)
}
if (h.direction == "in") {
if (h.connected) {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_down_green, time))
} else {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_down_red, time))
}
} else {
if (h.connected) {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_up_green, time))
} else {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_up_red, time))
}
}
posAtHistory.add(i)
}
}
}
private fun isToday(time: GregorianCalendar): Boolean {
val now = GregorianCalendar()
return now.get(Calendar.YEAR) == time.get(Calendar.YEAR) &&
now.get(Calendar.MONTH) == time.get(Calendar.MONTH) &&
now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH)
}
companion object {
var History: ArrayList<History> = ArrayList<History>()
}
}
@@ -0,0 +1,29 @@
package com.tutpro.baresip
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import java.util.ArrayList
class HistoryListAdapter(private val cxt: Context, private val rows: ArrayList<HistoryRow>) :
ArrayAdapter<HistoryRow>(cxt, R.layout.history_row, rows) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val row = rows[position]
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rowView = inflater.inflate(R.layout.history_row, parent, false)
val directionView = rowView.findViewById(R.id.direction) as ImageView
directionView.setImageResource(row.direction)
val peerURIView = rowView.findViewById(R.id.peer_uri) as TextView
peerURIView.text = row.peerURI
val timeView = rowView.findViewById(R.id.time) as TextView
timeView.text = row.time
return rowView
}
}
@@ -0,0 +1,3 @@
package com.tutpro.baresip
class HistoryRow(val peerURI: String, val direction: Int, val time: String)
@@ -0,0 +1,668 @@
package com.tutpro.baresip
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.*
import android.widget.RelativeLayout.LayoutParams
import android.widget.AutoCompleteTextView
import android.view.*
import android.util.Log
import java.util.*
import java.io.*
import android.widget.RelativeLayout
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainActivityContext = applicationContext
layout = findViewById(R.id.mainActivityLayout) as RelativeLayout
callee = findViewById(R.id.callee) as AutoCompleteTextView
callButton = findViewById(R.id.callButton) as Button
holdButton = findViewById(R.id.holdButton) as Button
AoRSpinner = findViewById(R.id.AoRList) as Spinner
Log.i("Baresip", "Setting AccountAdapter")
AccountAdapter = AccountSpinnerAdapter(applicationContext, AoRs, Images)
AoRSpinner.adapter = AccountAdapter
AoRSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val aor = AoRs[position]
val callee = findViewById(R.id.callee) as AutoCompleteTextView
val callButton = findViewById(R.id.callButton) as Button
val holdButton = findViewById(R.id.holdButton) as Button
Log.i("Baresip", "Setting $aor current")
val out = uaCalls(Out, aor_ua(aor))
if (out.size == 0) {
callee.text.clear()
callee.hint = "Callee"
callButton.text = "Call"
if (aorHasHistory(aor)) {
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
} else {
holdButton.visibility = View.INVISIBLE
}
} else {
callee.setText(out[0].peerURI)
callButton.text = out[0].status
if (out[0].status == "Hangup") {
if (out[0].hold) {
holdButton.text = "Unhold"
} else {
holdButton.text = "Hold"
}
holdButton.visibility = View.VISIBLE
} else {
holdButton.visibility = View.INVISIBLE
}
}
val `in` = uaCalls(In, aor_ua(aor))
val view_count = layout.childCount
Log.d("Baresip", "View count is $view_count")
if (view_count > 5) {
layout.removeViews(5, view_count - 5)
}
for (c in `in`) {
for (call_index in In.indices) {
addCallViews(c, (call_index + 1) * 10)
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
Log.i("Baresip", "Nothing selected")
}
}
val assets = arrayOf("accounts", "contacts", "config", "busy.wav", "callwaiting.wav", "error.wav", "message.wav", "notfound.wav", "ring.wav", "ringback.wav")
val path = applicationContext.filesDir.path
Log.d("Baresip", "path is: $path")
var file = File(path)
if (!file.exists()) {
Log.d("Baresip", "Creating baresip directory")
try {
File(path).mkdirs()
} catch (e: Error) {
Log.e("Baresip", "Failed to create directory: " + e.toString())
}
}
for (a in assets) {
file = File("$path/$a")
if (!file.exists()) {
Log.d("Baresip", "Copying asset $a")
copyAssetToFile(a, "$path/$a")
} else {
Log.d("Baresip", "Asset $a already copied")
}
}
file = File(path, "history")
try {
val fis = FileInputStream(file)
val ois = ObjectInputStream(fis)
@SuppressWarnings("unchecked")
HistoryActivity.History = ois.readObject() as ArrayList<History>
Log.d("Baresip", "Restored History of " + HistoryActivity.History.size + " entries")
ois.close()
fis.close()
} catch (e: Exception) {
Log.w("Baresip", "InputStream exception: - " + e.toString())
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "Baresip does not have RECORD_AUDIO permission")
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION)
}
if (!running) {
Log.i("Baresip", "Starting Baresip with path $path")
Thread(Runnable { baresipStart(path) }).start()
running = true
}
EditContactsActivity.updateContactsAndNames(applicationContext.filesDir.absolutePath + "/contacts")
CalleeAdapter = ArrayAdapter(this, android.R.layout.select_dialog_item, EditContactsActivity.Names)
callee.threshold = 2
callee.setAdapter<ArrayAdapter<String>>(CalleeAdapter)
callButton.text = "Call"
callButton.setOnClickListener {
Log.d("Baresip", "AoR at position is " + AoRs[AoRSpinner.selectedItemPosition])
val aor = AoRs[AoRSpinner.selectedItemPosition]
if (callButton.text.toString() == "Call") {
call(aor, (findViewById(R.id.callee) as EditText).text.toString())
} else {
Log.i("Baresip", "Canceling UA " + aor_ua(aor) + " call " +
Out[0].call + " to " +
(findViewById(R.id.callee) as EditText).text)
ua_hangup(aor_ua(aor), Out[0].call, 486, "Rejected")
}
}
val holdButton = findViewById(R.id.holdButton) as Button
holdButton.setOnClickListener {
when (holdButton.text.toString()) {
"Hold" -> {
Log.i("Baresip", "Holding up " + (findViewById(R.id.callee) as EditText).text)
call_hold(Out[0].call)
Out[0].hold = true
holdButton.text = "Unhold"
}
"Unhold" -> {
Log.i("Baresip", "Unholding " + (findViewById(R.id.callee) as EditText).text)
call_unhold(Out[0].call)
Out[0].hold = false
holdButton.text = "Hold"
}
"History" -> {
val i = Intent(this@MainActivity, HistoryActivity::class.java)
val b = Bundle()
b.putString("aor", AoRs[AoRSpinner.selectedItemPosition])
i.putExtras(b)
startActivityForResult(i, HISTORY_CODE)
}
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d("Baresip", "Screen orientation change to landscape")
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d("Baresip", "Screen orientation change to portrait")
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
RECORD_AUDIO_PERMISSION -> {
if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "RECORD_AUDIO permission granted")
} else {
Log.d("Baresip", "RECORD_AUDIO permission NOT granted")
}
return
}
else -> Log.e("Baresip", "Unknown permissions request code: $requestCode")
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.main_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i: Intent
when (item.itemId) {
R.id.accounts -> {
i = Intent(this, EditAccountsActivity::class.java)
startActivityForResult(i, EDIT_ACCOUNTS_CODE)
return true
}
R.id.contacts -> {
i = Intent(this, EditContactsActivity::class.java)
startActivityForResult(i, EDIT_CONTACTS_CODE)
return true
}
R.id.config -> {
i = Intent(this, EditConfigActivity::class.java)
startActivityForResult(i, EDIT_CONFIG_CODE)
return true
}
R.id.about -> {
i = Intent(this, AboutActivity::class.java)
startActivityForResult(i, ABOUT_CODE)
return true
}
R.id.quit -> {
if (running) {
Log.d("Baresip", "Stopping")
val path = applicationContext.filesDir.path
Log.d("Baresip", "Saving history to $path")
val file = File(path, "history")
try {
val fos = FileOutputStream(file)
val oos = ObjectOutputStream(fos)
oos.writeObject(HistoryActivity.History)
oos.close()
fos.close()
} catch (e: IOException) {
Log.w("Baresip", "OutputStream exception: " + e.toString())
e.printStackTrace()
}
baresipStop()
running = false
}
finish()
System.exit(0)
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == EDIT_ACCOUNTS_CODE) {
if (resultCode == RESULT_OK) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle("Alert")
alertDialog.setMessage("You need to restart baresip in order to activate saved accounts!")
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK"
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit accounts canceled")
}
}
if (requestCode == EDIT_CONFIG_CODE) {
if (resultCode == RESULT_OK) {
Utils.alertView(this,
"You need to restart baresip in order to activate saved config!")
reload_config()
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit config canceled")
}
}
if (requestCode == HISTORY_CODE) {
if (resultCode == RESULT_OK) {
if (data != null) {
(findViewById(R.id.callee) as EditText).setText(data.getStringExtra("peer_uri"))
}
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "History canceled")
if (!aorHasHistory(AoRs[AoRSpinner.selectedItemPosition])) {
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
}
if (requestCode == EDIT_CONTACTS_CODE || requestCode == ABOUT_CODE) {
Log.d("Baresip", "Back arrow or Cancel pressed at request: $requestCode")
}
}
fun addAccount(ua: String) {
val aor = ua_aor(ua)
Log.d("Baresip", "Adding account $ua with AoR $aor")
Accounts.add(Account(ua, aor, ""))
AoRs.add(aor)
Images.add(R.drawable.yellow)
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
private fun call(aor: String, callee: String) {
if (callee.length == 0) return
var uri = EditContactsActivity.findContactURI(callee)
if (!uri.startsWith("sip:")) uri = "sip:$uri"
if (!uri.contains("@")) {
val host = aor.substring(aor.indexOf("@") + 1)
uri = "$uri@$host"
}
(findViewById(R.id.callee) as EditText).setText(uri)
val ua = aor_ua(aor)
Log.i("Baresip", "Calling $ua / $uri")
val call = ua_connect(ua, uri)
if (call != "") {
Log.i("Baresip", "Adding outgoing call $ua / $call / $uri")
Out.add(Call(ua, call, uri, "Cancel"))
(findViewById(R.id.callButton) as Button).text = "Cancel"
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
private fun copyAssetToFile(asset: String, path: String) {
try {
val assetManager = assets
val `is` = assetManager.open(asset)
val os = FileOutputStream(path)
val buffer = ByteArray(512)
var byteRead: Int = `is`.read(buffer)
while (byteRead != -1) {
os.write(buffer, 0, byteRead)
byteRead = `is`.read(buffer)
}
} catch (e: IOException) {
Log.e("Baresip", "Failed to read asset " + asset + ": " +
e.toString())
}
}
private fun addCallViews(call: Call, id: Int) {
Log.d("Baresip", "Creating new Incoming textview at $id")
val caller_heading = TextView(mainActivityContext)
caller_heading.text = "Incoming call from ..."
caller_heading.setTextColor(Color.BLACK)
caller_heading.textSize = 20f
caller_heading.setPadding(10, 20, 0, 0)
caller_heading.id = id
val heading_params = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
if (id == 10)
heading_params.addRule(RelativeLayout.BELOW, callButton.id)
else
heading_params.addRule(RelativeLayout.BELOW, id - 10 + 3)
caller_heading.layoutParams = heading_params
layout.addView(caller_heading)
val caller_uri = TextView(mainActivityContext)
caller_uri.text = In[In.size - 1].peerURI
caller_uri.setTextColor(Color.GREEN)
caller_uri.textSize = 20f
caller_uri.setPadding(10, 10, 0, 10)
Log.d("Baresip", "Creating new caller textview at " + (id + 1))
caller_uri.id = id + 1
val caller_uri_params = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
caller_uri_params.addRule(RelativeLayout.BELOW, id)
caller_uri.layoutParams = caller_uri_params
layout.addView(caller_uri)
val answer_button = Button(mainActivityContext)
answer_button.text = call.status
answer_button.setBackgroundResource(android.R.drawable.btn_default)
answer_button.setTextColor(Color.BLACK)
Log.d("Baresip", "Creating new answer button at " + (id + 2))
answer_button.id = id + 2
answer_button.setOnClickListener { v ->
val call_ua = call.ua
val call_call = call.call
when ((v as Button).text.toString()) {
"Answer" -> {
Log.i("Baresip", "UA " + call_ua + " accepting incoming call " +
call_call)
ua_answer(call_ua, call_call)
val final_in_index = callIndex(In, call_ua, call_call)
if (final_in_index >= 0) {
Log.d("Baresip", "Updating Hangup and Hold")
In[final_in_index].status = "Hangup"
In[final_in_index].hold = false
runOnUiThread {
val answer_id = (final_in_index + 1) * 10 + 2
val answer_but = layout.findViewById(answer_id) as Button
answer_but.text = "Hangup"
val reject_button = layout.findViewById(answer_id + 1) as Button
reject_button.text = "Hold"
}
}
}
"Hangup" -> {
Log.i("Baresip", "UA " + call_ua + " hanging up call " +
call_call)
ua_hangup(call_ua, call_call, 200, "OK")
}
else -> Log.e("Baresip", "Invalid answer button text: " + v.text.toString())
}
}
val answer_button_params = LayoutParams(200,
LayoutParams.WRAP_CONTENT)
answer_button_params.addRule(RelativeLayout.BELOW, id + 1)
answer_button_params.setMargins(3, 10, 0, 0)
answer_button.layoutParams = answer_button_params
layout.addView(answer_button)
val reject_button = Button(mainActivityContext)
if (call.status == "Answer") {
reject_button.text = "Reject"
} else {
if (call.hold) {
reject_button.text = "Unhold"
} else {
reject_button.text = "Hold"
}
}
reject_button.setBackgroundResource(android.R.drawable.btn_default)
reject_button.setTextColor(Color.BLACK)
Log.d("Baresip", "Creating new reject button at " + (id + 3))
reject_button.id = id + 3
reject_button.setOnClickListener { v ->
when ((v as Button).text.toString()) {
"Reject" -> {
Log.i("Baresip", "UA " + call.ua +
" rejecting incoming call " + call.call)
ua_hangup(call.ua, call.call, 486, "Rejected")
}
"Hold" -> {
call_hold(call.call)
v.text = "Unhold"
call.hold = true
}
"Unhold" -> {
call_unhold(call.call)
v.text = "Hold"
call.hold = false
}
}
}
val reject_button_params = LayoutParams(200,
LayoutParams.WRAP_CONTENT)
reject_button_params.addRule(RelativeLayout.BELOW, id + 1)
reject_button_params.setMargins(225, 10, 0, 0)
reject_button.layoutParams = reject_button_params
layout.addView(reject_button)
}
private fun callIndex(calls: ArrayList<Call>, ua: String, call: String): Int {
for (i in calls.indices) {
if (calls[i].ua.equals(ua) && calls[i].call == call)
return i
}
return -1
}
private fun uaCalls(calls: ArrayList<Call>, ua: String): ArrayList<Call> {
val result = ArrayList<Call>()
for (i in calls.indices) {
if (calls[i].ua == ua) result.add(calls[i])
}
return result
}
private fun aorHasHistory(aor: String): Boolean {
for (h in HistoryActivity.History) {
if (h.aor == aor) return true
}
return false
}
private fun callHasHistory(ua: String, call: String): Boolean {
for (h in HistoryActivity.History) {
if (h.ua == ua && h.call == call) return true
}
return false
}
private fun updateStatus(event: String, ua: String, call: String) {
val aor = ua_aor(ua)
var call_index: Int
Log.d("Baresip", "Handling event " + event + " for " + ua + "/" + call + "/" +
aor)
for (account_index in Accounts.indices) {
if (Accounts[account_index].aoR == aor) {
Log.d("Baresip", "Found AoR at index $account_index")
when (event) {
"registering", "unregistering" -> {
}
"registered" -> {
Log.d("Baresip", "Setting status to green")
Accounts[account_index].status = "OK"
AoRs[account_index] = aor
Images[account_index] = R.drawable.green
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
"registering failed" -> {
Log.d("Baresip", "Setting status to red")
Accounts[account_index].status = "FAIL"
AoRs[account_index] = aor
Images[account_index] = R.drawable.red
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
"call ringing" -> {
}
"call established" -> {
val out_index = callIndex(Out, ua, call)
if (out_index >= 0) {
Log.d("Baresip", "Outbound call " + call + " established")
Out[out_index].status = "Hangup"
Out[out_index].hold = false
runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
callButton.text = "Hangup"
holdButton.text = "Hold"
holdButton.visibility = View.VISIBLE
}
}
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"out", true))
} else {
Log.d("Baresip", "Inbound call " + call + " established")
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"in", true))
}
}
"call incoming" -> {
val peer_uri = call_peeruri(call)
Log.d("Baresip", "Incoming call " + ua + "/" + call + "/" +
peer_uri)
val new_call = Call(ua, call, peer_uri, "Answer")
In.add(new_call)
this@MainActivity.runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
addCallViews(new_call, In.size * 10)
}
}
}
"call closed" -> {
call_index = callIndex(In, ua, call)
if (call_index != -1) {
Log.d("Baresip", "Removing inbound call " + ua + "/" +
call + "/" + In[call_index].peerURI)
val view_id = (call_index + 1) * 10
val remove_count = In.size - call_index
In.removeAt(call_index)
this@MainActivity.runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
if (callButton.text == "Call") {
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
}
val caller_heading = layout.findViewById(view_id)
val view_index = layout.indexOfChild(caller_heading)
Log.d("Baresip", "Index of caller heading is $view_index")
layout.removeViews(view_index, 4 * remove_count)
for (i in call_index until In.size) {
this@MainActivity.addCallViews(In[i], (i + 1) * 10)
}
}
}
if (!callHasHistory(ua, call)) {
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"in", false))
}
} else {
call_index = callIndex(Out, ua, call)
if (call_index != -1) {
Log.d("Baresip", "Removing outgoing call " + ua + "/" +
call + "/" + Out[call_index].peerURI)
Out.removeAt(call_index)
runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
callButton.text = "Call"
callButton.isEnabled = true
callee.setText("")
callee.hint = "Callee"
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
}
}
if (!callHasHistory(ua, call)) {
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"out", false))
}
} else {
Log.e("Baresip", "Unknown call " + ua + "/" + call +
" closed")
}
}
}
else -> Log.d("Baresip", "Unknown event '$event'")
}
}
}
}
external fun baresipStart(path: String)
external fun baresipStop()
external fun call_peeruri(call: String): String
external fun ua_aor(ua: String): String
external fun aor_ua(aor: String): String
external fun ua_connect(ua: String, peer_uri: String): String
external fun ua_answer(ua: String, call: String)
external fun call_hold(call: String): Int?
external fun call_unhold(call: String): Int?
external fun ua_hangup(ua: String, call: String, code: Int, reason: String)
external fun reload_config(): Int
companion object {
internal lateinit var mainActivityContext: Context
internal lateinit var layout: RelativeLayout
internal lateinit var callee: AutoCompleteTextView
internal lateinit var callButton: Button
internal lateinit var holdButton: Button
internal lateinit var AccountAdapter: AccountSpinnerAdapter
internal lateinit var AoRSpinner: Spinner
internal var running: Boolean = false
internal var Accounts = ArrayList<Account>()
internal var AoRs = ArrayList<String>()
internal var Images = ArrayList<Int>()
internal var CalleeAdapter: ArrayAdapter<String>? = null
internal var In = ArrayList<Call>()
internal var Out = ArrayList<Call>()
private val RECORD_AUDIO_PERMISSION = 1
private val EDIT_ACCOUNTS_CODE = 1
private val EDIT_CONTACTS_CODE = 2
private val EDIT_CONFIG_CODE = 3
private val HISTORY_CODE = 4
private val ABOUT_CODE = 5
external fun contacts_remove()
external fun contact_add(contact: String)
init {
System.loadLibrary("baresip")
}
}
}
@@ -0,0 +1,67 @@
package com.tutpro.baresip
import android.content.Context
import android.content.DialogInterface
import android.support.v7.app.AlertDialog
import android.util.Log
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
object Utils {
fun getFileContents(file: File): String {
if (!file.exists()) {
Log.e("Baresip", "Failed to find file: " + file.path)
return ""
} else {
Log.e("Baresip", "Found file: " + file.path)
val length = file.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
return String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read file: " + file.path + ": " +
e.toString())
return ""
}
}
}
fun putFileContents(file: File, contents: String) {
try {
val fOut = FileOutputStream(file.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
try {
fWriter.write(contents)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to put contents to file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find contents file: " + e.toString())
}
}
fun alertView(context: Context, message: String) {
val alertDialog = AlertDialog.Builder(context).create()
alertDialog.setTitle("Alert")
alertDialog.setMessage(message)
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK"
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
}
}