Files
2018-08-07 16:13:17 +03:00

1162 lines
53 KiB
Kotlin

package com.tutpro.baresip
import android.Manifest
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Intent
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.util.Log
import android.view.Menu
import android.view.MenuItem
import android.content.Context
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.media.AudioManager
import android.media.Ringtone
import android.media.RingtoneManager
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.TextWatcher
import android.widget.RelativeLayout.LayoutParams
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 callee: AutoCompleteTextView
internal lateinit var securityButton: ImageButton
internal lateinit var callButton: ImageButton
internal lateinit var holdButton: ImageButton
internal lateinit var contactsButton: ImageButton
internal lateinit var messagesButton: ImageButton
internal lateinit var callsButton: ImageButton
internal lateinit var dtmf: EditText
internal lateinit var uaAdapter: UaSpinnerAdapter
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
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("Baresip", "At onCreate")
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES)
setContentView(R.layout.activity_main)
filesPath = applicationContext.filesDir.absolutePath
appContext = applicationContext
layout = findViewById(R.id.mainActivityLayout) as RelativeLayout
callee = findViewById(R.id.callee) as AutoCompleteTextView
securityButton = findViewById(R.id.securityButton) as ImageButton
callButton = findViewById(R.id.callButton) as ImageButton
holdButton = findViewById(R.id.holdButton) as ImageButton
contactsButton = findViewById(R.id.contactsButton) as ImageButton
messagesButton = findViewById(R.id.messagesButton) as ImageButton
callsButton = findViewById(R.id.callsButton) as ImageButton
dtmf = findViewById(R.id.dtmf) as EditText
am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val rtUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,
RingtoneManager.TYPE_RINGTONE)
rt = RingtoneManager.getRingtone(applicationContext, rtUri)
imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
serviceEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handleServiceEvent(intent.getStringExtra("event"),
intent.getStringArrayListExtra("params"))
}
}
LocalBroadcastManager.getInstance(this).registerReceiver(serviceEventReceiver,
IntentFilter("service event"))
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
Log.w("Baresip", "Baresip does not have RECORD_AUDIO permission")
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION)
}
aorSpinner = findViewById(R.id.AoRList)
uaAdapter = UaSpinnerAdapter(applicationContext, uas, images)
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 aor = acc.aor
val ua = uas[position]
Log.d("Baresip", "Setting $aor current")
uag_current_set(uas[position].uap)
val callsOut = Call.uaCalls(calls, ua, "out")
if (callsOut.size == 0) {
callee.text.clear()
callee.hint = "Callee"
callButton.tag = "Call"
callButton.setImageResource(R.drawable.call_green)
securityButton.visibility = View.INVISIBLE
dtmf.visibility = View.INVISIBLE
} else {
callee.setText(callsOut[0].peerURI)
callButton.tag = callsOut[0].status
if (callsOut[0].status == "Hangup") {
if (callsOut[0].hold) {
holdButton.setImageResource(R.drawable.play)
} else {
holdButton.setImageResource(R.drawable.pause)
}
holdButton.visibility = View.VISIBLE
securityButton.setImageResource(callsOut[0].security)
setSecurityButtonTag(securityButton, callsOut[0].security)
if ((acc.mediaenc == "zrtp") || (acc.mediaenc == "dtls_srtpf"))
securityButton.visibility = View.VISIBLE
else
securityButton.visibility = View.INVISIBLE
dtmf.visibility = View.VISIBLE
dtmf.requestFocus()
} else {
holdButton.visibility = View.INVISIBLE
securityButton.visibility = View.INVISIBLE
dtmf.visibility = View.INVISIBLE
}
}
val view_count = layout.childCount
// Log.d("Baresip", "View count is $view_count")
if (view_count > 6)
layout.removeViews(6, view_count - 6)
for (c in Call.uaCalls(calls, ua, "in"))
for (call_index in Call.uaCalls(calls, ua, "in").indices) {
val startIndex = (call_index + 1) * 10
addCallViews(ua, c, startIndex)
if (answerCall == c.callp) {
answerCall = ""
val answerButton = layout.findViewById(startIndex + 5) as ImageButton
answerButton.performClick()
}
if (rejectCall == c.callp) {
rejectCall = ""
val rejectButton = layout.findViewById(startIndex + 6) as ImageButton
rejectButton.performClick()
moveTaskToBack(true)
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
Log.d("Baresip", "Nothing selected")
}
}
aorSpinner.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if (aorSpinner.selectedItemPosition == -1) {
val i = Intent(this, AccountsActivity::class.java)
startActivityForResult(i, ACCOUNTS_CODE)
true
} else {
if ((event.x - view.left) < 100) {
val i = Intent(this, AccountActivity::class.java)
val b = Bundle()
b.putString("accp", uas[aorSpinner.selectedItemPosition].account.accp)
i.putExtras(b)
startActivityForResult(i, ACCOUNT_CODE)
true
} else {
false
}
}
} else {
false
}
}
securityButton.setOnClickListener {
when (securityButton.tag) {
"red" -> {
Utils.alertView(this, "Alert", "This call is NOT secure!")
}
"yellow" -> {
Utils.alertView(this, "Alert",
"This call is SECURE, but peer is NOT verified!")
}
"green" -> {
val unverifyDialog = AlertDialog.Builder(this)
unverifyDialog.setTitle("Info")
unverifyDialog.setMessage("This call is SECURE and peer is VERIFIED! " +
"Do you want to unverify the peer?")
unverifyDialog.setPositiveButton("Unverify") { dialog, _ ->
val outCalls = Call.uaCalls(calls, uas[aorSpinner.selectedItemPosition], "out")
if (outCalls.size > 0) {
if (Api.cmd_exec("zrtp_unverify " + outCalls[0].zid) != 0) {
Log.w("Baresip",
"Command 'zrtp_unverify ${outCalls[0].zid}' failed")
} else {
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
}
}
dialog.dismiss()
}
unverifyDialog.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}
unverifyDialog.create().show()
}
}
}
callButton.tag = "Call"
callButton.setOnClickListener {
val ua = uas[aorSpinner.selectedItemPosition]
val aor = ua.account.aor
when (callButton.tag) {
"Call" -> {
if (!ONE_CALL_ONLY || calls.isEmpty()) {
val calleeText = (findViewById(R.id.callee) as EditText).text.toString()
.trim()
if (calleeText.length > 0) {
var uri = ContactsActivity.findContactURI(calleeText)
if (!uri.startsWith("sip:")) uri = "sip:$uri"
if (!uri.contains("@")) {
val host = aor.substring(aor.indexOf("@") + 1)
uri = "$uri@$host"
}
call(ua, uri)
} else {
val latest = CallHistory.aorLatestHistory(history, aor)
if (latest != null)
if (Utils.uriHostPart(latest.peerURI) == Utils.uriHostPart(aor))
callee.setText(Utils.uriUserPart(latest.peerURI))
else
callee.setText(latest.peerURI)
}
}
}
"Cancel" -> {
val callp = Call.calls(calls, "out")[0].callp
Log.d("Baresip", "Canceling AoR $aor call $callp to " +
(findViewById(R.id.callee) as EditText).text)
ua_hangup(ua.uap, callp, 486, "Rejected")
}
"Hangup" -> {
val callp = Call.calls(calls, "out")[0].callp
Log.d("Baresip", "Hanging up AoR $aor call $callp to " +
(findViewById(R.id.callee) as EditText).text)
callButton.isEnabled = false
ua_hangup(ua.uap, callp, 0, "")
}
}
}
holdButton.tag = "Hold"
holdButton.visibility = View.INVISIBLE
holdButton.setOnClickListener {
when (holdButton.tag) {
"Hold" -> {
holdCallAt(Call.calls(calls, "out")[0], holdButton)
}
"Resume" -> {
Log.d("Baresip", "Resuming " + (findViewById(R.id.callee) as EditText).text)
val call = Call.calls(calls, "out")[0]
call_unhold(call.callp)
call.hold = false
holdButton.tag = "Hold"
holdButton.setImageResource(R.drawable.pause)
}
}
}
contactsButton.setOnClickListener {
val i = Intent(this@MainActivity, ContactsActivity::class.java)
val b = Bundle()
if (aorSpinner.selectedItemPosition >= 0)
b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor)
else
b.putString("aor", "")
i.putExtras(b)
startActivityForResult(i, CONTACTS_CODE)
}
messagesButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
val i = Intent(this@MainActivity, MessagesActivity::class.java)
val b = Bundle()
b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor)
i.putExtras(b)
startActivityForResult(i, MESSAGES_CODE)
}
}
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)
i.putExtras(b)
startActivityForResult(i, HISTORY_CODE)
}
}
baresipService = Intent(this@MainActivity, BaresipService::class.java)
if (!BaresipService.IS_SERVICE_RUNNING) {
baresipService.setAction("Start")
startService(baresipService)
}
callee.threshold = 2
callee.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
ContactsActivity.contacts.map { Contact -> Contact.name }))
if (intent.hasExtra("onStartup"))
moveTaskToBack(true)
}
private fun handleServiceEvent(event: String, params: ArrayList<String>) {
if (event == "exit") {
if (BaresipService.IS_SERVICE_RUNNING) {
baresipService.setAction("Stop");
startService(baresipService)
}
Toast.makeText(applicationContext,
"Baresip has stopped! Check your network connectivity.",
Toast.LENGTH_SHORT).show()
finish()
return
}
val uap = params[0]
val ua = UserAgent.find(uas, uap)
if (ua == null) {
Log.w("Baresip", "handleServiceEvent '$event' did not find ua $uap")
return
}
val ev = event.split(",")
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) {
when (ev[0]) {
"ua added" -> {
uaAdapter.notifyDataSetChanged()
if (aorSpinner.selectedItemPosition == -1) aorSpinner.setSelection(0)
}
"registered", "registering failed", "unregistering" -> {
uaAdapter.notifyDataSetChanged()
}
"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")
return
}
val peer_uri = Api.call_peeruri(callp)
val new_call = Call(callp, ua, peer_uri, "in", "Answer", null)
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))
} else {
Log.d("Baresip", "Incoming call $uap/$callp/$peer_uri")
calls.add(new_call)
if (ua != uas[aorSpinner.selectedItemPosition])
aorSpinner.setSelection(account_index)
else
addCallViews(ua, new_call, Call.uaCalls(calls, ua, "in").size * 10)
startRinging()
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)
if (call == null) {
Log.e("Baresip", "Established call $callp not found")
return
}
if (call.dir == "out") {
Log.d("Baresip", "Outbound call $callp established")
call.status = "Hangup"
call.hold = false
call.security = R.drawable.box_red
if (ua == uas[aorSpinner.selectedItemPosition]) {
callButton.tag = "Hangup"
callButton.setImageResource(R.drawable.hangup)
holdButton.tag = "Hold"
holdButton.setImageResource(R.drawable.pause)
holdButton.visibility = View.VISIBLE
if (acc.mediaenc == "") {
securityButton.visibility = View.INVISIBLE
} else {
securityButton.setImageResource(R.drawable.box_red)
securityButton.tag = "red"
securityButton.visibility = View.VISIBLE
}
dtmf.setText("")
dtmf.hint = "DTMF"
dtmf.visibility = View.VISIBLE
dtmf.requestFocus()
dtmf.addTextChangedListener(call.dtmfWatcher)
}
history.add(CallHistory(aor, call.peerURI, "out", true))
call.hasHistory = true
} else {
Log.d("Baresip", "Inbound call $callp established")
call.status = "Hangup"
call.security = R.drawable.box_red
if (ua == uas[aorSpinner.selectedItemPosition]) {
if (acc.mediaenc != "") {
val inIndex = Call.uaCallIndex(calls, ua, call, "in")
val view_id = (inIndex + 1) * 10 + 3
val securityButton = findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_red)
securityButton.tag = "red"
securityButton.visibility = View.VISIBLE
val rejectButton = findViewById(view_id + 3) as ImageButton
rejectButton.tag = "Hold"
rejectButton.setImageResource(R.drawable.pause)
}
}
history.add(CallHistory(aor, call.peerURI, "in", true))
call.hasHistory = true
}
if (rtTimer != null) {
rtTimer!!.cancel()
rtTimer = null
if (rt.isPlaying) rt.stop()
}
am.mode = AudioManager.MODE_IN_COMMUNICATION
am.isSpeakerphoneOn = false
// am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
// (am.getStreamMaxVolume(STREAM_VOICE_CALL) / 2) + 1,
// AudioManager.STREAM_VOICE_CALL)
if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE)
CallHistory.aorRemoveHistory(history, aor)
}
"call verify" -> {
val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp to be verified is not found")
return
}
val verifyDialog = AlertDialog.Builder(this@MainActivity)
verifyDialog.setTitle("Verify")
verifyDialog.setMessage("Do you want to verify SAS <${ev[1]}> <${ev[2]}>?")
verifyDialog.setPositiveButton("Yes") { dialog, _ ->
val security: Int
if (Api.cmd_exec("zrtp_verify ${ev[3]}") != 0) {
Log.w("Baresip", "Command 'zrtp_verify ${ev[3]}' failed")
security = R.drawable.box_yellow
} else {
security = R.drawable.box_green
}
call.security = security
call.zid = ev[3]
if (call.dir == "out") {
securityButton.setImageResource(security)
setSecurityButtonTag(securityButton, security)
securityButton.visibility = View.VISIBLE
} else {
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.uaCallIndex(calls, ua, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(security)
setSecurityButtonTag(securityButton, security)
securityButton.visibility = View.VISIBLE
}
}
dialog.dismiss()
}
verifyDialog.setNegativeButton("No") { dialog, _ ->
call.security = R.drawable.box_yellow
call.zid = ev[3]
if (call.dir == "out") {
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
securityButton.visibility = View.VISIBLE
} else {
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.uaCallIndex(calls, ua, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(R.drawable.box_yellow)
securityButton.tag = "yellow"
securityButton.visibility = View.VISIBLE
}
}
dialog.dismiss()
}
verifyDialog.create().show()
}
"call verified", "call secure" -> {
val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp that is verified is not found")
return
}
val tag: String
if (ev[0] == "call secure") {
call.security = R.drawable.box_yellow
tag = "yellow"
} else {
call.security = R.drawable.box_green
tag = "green"
call.zid = ev[1]
}
if (call.dir == "out") {
if (ua == uas[aorSpinner.selectedItemPosition]) {
securityButton.setImageResource(call.security)
securityButton.tag = tag
securityButton.visibility = View.VISIBLE
}
} else {
if (ua == uas[aorSpinner.selectedItemPosition]) {
val view_id = (Call.uaCallIndex(calls, ua, call, "in") + 1) * 10 + 3
val securityButton = layout.findViewById(view_id) as ImageButton
securityButton.setImageResource(call.security)
securityButton.tag = tag
securityButton.visibility = View.VISIBLE
}
}
}
"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
}
if (call.dir == "in") {
Log.d("Baresip", "Removing inbound call ${uap}/${callp}/" +
call.peerURI)
val inIndex = Call.uaCallIndex(calls, ua, call, "in")
val view_id = (inIndex + 1) * 10
val remove_count = Call.uaCalls(calls, ua, "in").size - inIndex
calls.remove(call)
if (ua == uas[aorSpinner.selectedItemPosition]) {
if (callButton.tag == "Call") {
callee.setOnKeyListener(null)
}
val caller_heading = layout.findViewById(view_id) as TextView?
if (caller_heading != null) {
val view_index = layout.indexOfChild(caller_heading)
layout.removeViews(view_index, 3 * remove_count)
val callsIn = Call.uaCalls(calls, ua, "in")
for (i in inIndex until callsIn.size) {
addCallViews(ua, callsIn[i], (i + 1) * 10)
}
}
}
if (!call.hasHistory) {
if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE)
CallHistory.aorRemoveHistory(history, aor)
history.add(CallHistory(aor, call.peerURI, "in", false))
}
stopRinging()
} else {
Log.d("Baresip", "Removing outgoing call $uap/$callp/" +
call.peerURI)
if (ua == uas[aorSpinner.selectedItemPosition]) {
callButton.tag = "Call"
callButton.setImageResource(R.drawable.call_green)
callButton.isEnabled = true
callee.setText("")
callee.hint = "Callee"
holdButton.visibility = View.INVISIBLE
if (currentFocus == dtmf) {
imm.hideSoftInputFromWindow(dtmf.windowToken, 0)
}
securityButton.visibility = View.INVISIBLE
dtmf.removeTextChangedListener(call.dtmfWatcher)
dtmf.visibility = View.INVISIBLE
}
calls.remove(call)
if (!call.hasHistory) {
if (CallHistory.aorHistory(history, aor) > HISTORY_SIZE)
CallHistory.aorRemoveHistory(history, aor)
history.add(CallHistory(aor, call.peerURI, "out",
false))
}
}
if (calls.size == 0) am.mode = AudioManager.MODE_NORMAL
if (am.isSpeakerphoneOn) {
am.isSpeakerphoneOn = false
val speakerIcon = findViewById(R.id.speakerIcon) as ActionMenuItemView
speakerIcon.setBackgroundColor(ContextCompat.getColor(applicationContext,
R.color.colorPrimary))
}
}
"message" -> {
val peer_uri = params[1]
val msg = params[2]
val time = params[3]
val new_message = Message(aor, peer_uri, R.drawable.arrow_down_green, msg,
time.toLong(), true)
Log.d("Baresip", "Incoming message $aor/$peer_uri/$msg")
MessagesActivity.addMessage(new_message)
if (Utils.isVisible()) {
if (ua != uas[aorSpinner.selectedItemPosition])
aorSpinner.setSelection(account_index)
val i = Intent(applicationContext, MessagesActivity::class.java)
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val b = Bundle()
b.putString("aor", ua.account.aor)
i.putExtras(b)
startActivity(i)
}
}
else -> Log.w("Baresip", "Unknown event '${ev[0]}'")
}
break
}
}
}
override fun onNewIntent(intent: Intent) {
setIntent(intent)
val action = intent.getStringExtra("action")
Log.d("Baresip", "Got onNewIntent action $action")
when (action) {
"call" -> {
if (!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)
if (ua == null) {
Log.e("Baresip", "onNewIntent 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
}
}
}
makeCall = intent.getStringExtra("peer")
}
"answer" -> {
answerCall = intent.getStringExtra("callp")
}
"reject" -> {
rejectCall = intent.getStringExtra("callp")
moveTaskToBack(true)
}
"reply", "archive", "delete" -> {
val uap = intent.getStringExtra("uap")
val ua = UserAgent.find(MainActivity.uas, uap)
if (ua == null) {
Log.e("Baresip", "onNewIntent did not find ua $uap")
return
}
val aor = ua.account.aor
when (action) {
"reply" -> {
val i = Intent(this@MainActivity, MessagesActivity::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
}
}
}
b.putString("aor", aor)
b.putString("peer", intent.getStringExtra("peer"))
b.putBoolean("reply", true)
i.putExtras(b)
startActivityForResult(i, MESSAGES_CODE)
}
"archive" -> {
MessagesActivity.archiveUaMessage(ua.account.aor,
intent.getStringExtra("time").toLong())
moveTaskToBack(true)
}
"delete" -> {
MessagesActivity.deleteUaMessage(ua.account.aor,
intent.getStringExtra("time").toLong())
moveTaskToBack(true)
}
}
nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID)
}
}
}
override fun onPause() {
super.onPause()
// Log.d("Baresip", "Paused")
visible = false
}
override fun onResume() {
super.onResume()
// Log.d("Baresip", "Resumed")
imm.hideSoftInputFromWindow(callee.windowToken, 0)
visible = true
if ((answerCall != "") && (layout.childCount > 6)) {
answerCall = ""
/* if multiple calls, right answer button needs to be searched */
val answerButton = layout.findViewById(10 + 5) as ImageButton
answerButton.performClick()
}
if ((rejectCall != "") && (layout.childCount > 6)) {
rejectCall = ""
/* if multiple calls, right reject button needs to be searched */
val rejectButton = layout.findViewById(10 + 6) as ImageButton
rejectButton.performClick()
}
if (makeCall != "") {
callee.setText(makeCall)
makeCall = ""
callButton.performClick()
}
}
override fun onBackPressed() {
moveTaskToBack(true)
}
override fun onDestroy() {
Log.d("Baresip", "Destroyed")
LocalBroadcastManager.getInstance(this).unregisterReceiver(serviceEventReceiver)
if (BaresipService.IS_SERVICE_RUNNING) {
baresipService.setAction("Kill")
startService(baresipService)
}
super.onDestroy()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
menuInflater.inflate(R.menu.speaker_icon, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i: Intent
when (item.itemId) {
R.id.speakerIcon -> {
am.isSpeakerphoneOn = !am.isSpeakerphoneOn
val speakerIcon = findViewById(R.id.speakerIcon) as ActionMenuItemView
if (am.isSpeakerphoneOn)
speakerIcon.setBackgroundColor(Color.rgb(0x04, 0xb4, 0x04))
else
speakerIcon.setBackgroundColor(ContextCompat.getColor(applicationContext,
R.color.colorPrimary))
return true
}
R.id.accounts -> {
i = Intent(this, AccountsActivity::class.java)
startActivityForResult(i, ACCOUNTS_CODE)
return true
}
R.id.config -> {
i = Intent(this, EditConfigActivity::class.java)
startActivityForResult(i, EDIT_CONFIG_CODE)
return true
}
R.id.about -> {
Api.cmd_exec("audio_debug")
i = Intent(this, AboutActivity::class.java)
startActivityForResult(i, ABOUT_CODE)
return true
}
R.id.quit -> {
Log.d("Baresip", "Quiting")
if (BaresipService.IS_SERVICE_RUNNING) {
baresipService.setAction("Stop");
startService(baresipService)
}
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
ACCOUNTS_CODE -> {
uaAdapter.notifyDataSetChanged()
baresipService.setAction("UpdateNotification")
startService(baresipService)
}
CONTACTS_CODE -> {
callee.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
ContactsActivity.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()
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit config canceled")
}
}
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 (CallHistory.aorHistory(history, uas[aorSpinner.selectedItemPosition].account.aor) == 0) {
holdButton.visibility = View.INVISIBLE
}
}
}
ABOUT_CODE -> {
}
}
}
private fun call(ua: UserAgent, uri: String) {
if (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_DENIED) {
Toast.makeText(applicationContext,
"You have not granted microphone permission.", Toast.LENGTH_SHORT).show()
return
}
(findViewById(R.id.callee) as EditText).setText(uri)
val call = ua_connect(ua.uap, uri)
if (call != "") {
Log.d("Baresip", "Adding outgoing call ${ua.uap}/$call/$uri")
val dtmfWatcher = object : TextWatcher {
override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) {
val digit = sequence.subSequence(start, start + count).toString()
Log.d("Baresip", "Got DTMF digit '" + digit + "'")
if (digit.isNotEmpty()) call_send_digit(call, digit[0])
}
override fun afterTextChanged(sequence: Editable) {
call_send_digit(call, 4.toChar())
}
}
calls.add(Call(call, ua, uri, "out", "Cancel", dtmfWatcher))
callButton.tag = "Cancel"
callButton.setImageResource(R.drawable.hangup)
holdButton.visibility = View.INVISIBLE
} else {
Log.w("Baresip", "ua_connect ${ua.uap}/$uri failed")
}
}
private fun addCallViews(ua: UserAgent, call: Call, id: Int) {
Log.d("Baresip", "Adding incoming views for UA ${ua.uap} Call ${call.callp}")
val acc = ua.account
val caller_heading = TextView(appContext)
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 + 4)
caller_heading.layoutParams = heading_params
// Log.d("Baresip", "Adding incoming call heading at ${caller_heading.id}")
layout.addView(caller_heading)
val caller_row = LinearLayout(applicationContext)
caller_row.id = caller_heading.id + 1
caller_row.setOrientation(LinearLayout.HORIZONTAL)
val caller_row_params = LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
caller_row_params.addRule(RelativeLayout.BELOW, id)
caller_row.layoutParams = caller_row_params
val caller_uri = TextView(applicationContext)
caller_uri.text = ContactsActivity.contactName(call.peerURI)
caller_uri.setTextColor(Color.BLUE)
caller_uri.textSize = 20f
caller_uri.setPadding(10, 10, 0, 10)
caller_uri.id = caller_row.id + 1
val caller_uri_params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f)
caller_uri.layoutParams = caller_uri_params
// Log.d("Baresip", "Adding caller uri at ${caller_uri.id}")
caller_row.addView(caller_uri)
val security_button = ImageButton(applicationContext)
security_button.id = caller_uri.id + 1
val dp24px = ((24 * applicationContext.resources.displayMetrics.density) + 0.5).toInt()
val security_button_params = LinearLayout.LayoutParams(dp24px, dp24px, 0.0f)
security_button_params.gravity = Gravity.CENTER_VERTICAL
security_button.layoutParams = security_button_params
if ((call.security != 0) && (acc.mediaenc != "")) {
security_button.setImageResource(call.security)
setSecurityButtonTag(securityButton, call.security)
security_button.visibility = View.VISIBLE
} else {
security_button.visibility = View.INVISIBLE
}
security_button.setOnClickListener {
when (security_button.tag) {
"red" -> {
Utils.alertView(this, "Alert", "This call is NOT secure!")
}
"yellow" -> {
Utils.alertView(this, "Alert",
"This call is SECURE, but peer is NOT verified!")
}
"green" -> {
val unverifyDialog = AlertDialog.Builder(this)
unverifyDialog.setMessage("This call is SECURE and peer is VERIFIED! " +
"Do you want to unverify the peer?")
unverifyDialog.setPositiveButton("Unverify") { dialog, _ ->
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) {
Log.w("Baresip", "Command 'zrtp_unverify ${call.zid}' failed")
} else {
security_button.setImageResource(R.drawable.box_yellow)
security_button.tag = "yellow"
}
dialog.dismiss()
}
unverifyDialog.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}
unverifyDialog.create().show()
}
}
}
// Log.d("Baresip", "Adding security button at ${security_button.id}")
caller_row.addView(security_button)
// Log.d("Baresip", "Adding caller row at ${caller_row.id}")
layout.addView(caller_row)
val answer_row = LinearLayout(applicationContext)
answer_row.id = security_button.id + 1
answer_row.setOrientation(LinearLayout.HORIZONTAL)
val answer_row_params = LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
answer_row_params.addRule(RelativeLayout.BELOW, caller_row.id)
answer_row.layoutParams = answer_row_params
val answer_button = ImageButton(applicationContext)
answer_button.tag = call.status
if (call.status == "Answer")
answer_button.setImageResource(R.drawable.call_green)
else
answer_button.setImageResource(R.drawable.hangup)
answer_button.background = null
answer_button.id = answer_row.id + 1
answer_button.setOnClickListener { v ->
when ((v as ImageButton).tag) {
"Answer" -> {
Log.d("Baresip", "UA ${ua.uap} accepting incoming call ${call.callp}")
ua_answer(ua.uap, call.callp)
if (call.dir == "in") {
call.status = "Hangup"
call.hold = false
answer_button.tag = "Hangup"
answer_button.setImageResource(R.drawable.hangup)
val reject_button = layout.findViewById(answer_button.id + 1) as ImageButton
reject_button.tag = "Hold"
reject_button.setImageResource(R.drawable.pause)
}
}
"Hangup" -> {
Log.d("Baresip", "UA ${call.ua.uap} hanging up call ${call.callp}")
answer_button.isEnabled = false
ua_hangup(call.ua.uap, call.callp, 200, "OK")
}
else -> Log.e("Baresip", "Invalid answer button tag: " + v.tag)
}
}
val answer_button_params = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
answer_button_params.setMargins(3, 10, 0, 0)
answer_button.layoutParams = answer_button_params
answer_button.setPadding(0, 0, 0, 0)
// Log.d("Baresip", "Adding answer button at ${answer_button.id}")
answer_row.addView(answer_button)
val reject_button = ImageButton(appContext)
if (call.status == "Answer") {
reject_button.tag = "Reject"
reject_button.setImageResource(R.drawable.hangup)
} else {
if (call.hold) {
reject_button.tag = "Resume"
reject_button.setImageResource(R.drawable.play)
} else {
reject_button.tag = "Hold"
reject_button.setImageResource(R.drawable.pause)
}
}
reject_button.background = null
reject_button.id = answer_button.id + 1
reject_button.setOnClickListener { v ->
when ((v as ImageButton).tag) {
"Reject" -> {
Log.d("Baresip", "UA ${call.ua} rejecting incoming call ${call.callp}")
reject_button.isEnabled = false
answer_button.isEnabled = false
ua_hangup(call.ua.uap, call.callp, 486, "Rejected")
}
"Hold" -> {
holdCallAt(call, reject_button)
}
"Resume" -> {
call_unhold(call.callp)
v.tag = "Hold"
reject_button.setImageResource(R.drawable.pause)
call.hold = false
}
}
}
val reject_button_params = RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
reject_button_params.topMargin = 10
reject_button_params.addRule(RelativeLayout.ALIGN_PARENT_END)
reject_button.layoutParams = reject_button_params
reject_button.setPadding(50, 0, 0, 0)
reject_button.background = null
// Log.d("Baresip", "Adding reject button at ${reject_button.id}")
answer_row.addView(reject_button)
// Log.d("Baresip", "Adding answer row at ${answer_row.id}")
layout.addView(answer_row)
}
private fun setSecurityButtonTag(button: ImageButton, security: Int) {
when (security) {
R.drawable.box_red -> {
button.tag = "red"
}
R.drawable.box_yellow -> {
button.tag = "yellow"
}
R.drawable.box_green -> {
button.tag = "green"
}
}
}
private fun holdCallAt(call: Call, button: ImageButton) {
Log.d("Baresip", "Holding call with ${call.peerURI} at ${button.id}")
call_hold(call.callp)
call.hold = true
button.tag = "Resume"
button.setImageResource(R.drawable.play)
}
private fun startRinging() {
am.mode = AudioManager.MODE_RINGTONE
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(): String
external fun uag_current_set(uap: String)
external fun ua_connect(uap: String, peer_uri: String): String
external fun ua_answer(uap: String, callp: String)
external fun ua_hold_answer(uap: String, callp: String): Int
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 reload_config(): Int
companion object {
var uas = ArrayList<UserAgent>()
internal var images = ArrayList<Int>()
var history: ArrayList<CallHistory> = ArrayList()
internal var calls = ArrayList<Call>()
internal var messages = ArrayList<Message>()
var filesPath = ""
var visible = true
var makeCall = ""
var answerCall = ""
var rejectCall = ""
const val ACCOUNTS_CODE = 1
const val CONTACTS_CODE = 2
const val EDIT_CONFIG_CODE = 3
const val HISTORY_CODE = 4
const val ABOUT_CODE = 5
const val ACCOUNT_CODE = 6
const val 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
}
}