package com.tutpro.baresip.plus import android.Manifest.permission.* import android.annotation.SuppressLint import android.app.Activity import android.app.AlertDialog import android.app.KeyguardManager import android.app.NotificationManager import android.app.Presentation import android.content.* import android.content.Intent.ACTION_CALL import android.content.Intent.ACTION_DIAL import android.content.Intent.ACTION_VIEW import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.hardware.display.DisplayManager import android.media.AudioManager import android.media.MediaActionSound import android.net.Uri import android.os.* import android.provider.DocumentsContract import android.provider.MediaStore import android.text.InputType import android.text.TextWatcher import android.util.TypedValue import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.* import androidx.activity.OnBackPressedCallback import androidx.activity.enableEdgeToEdge import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.net.toUri import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.Observer import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.tutpro.baresip.plus.Utils.showSnackBar import com.tutpro.baresip.plus.databinding.ActivityMainBinding import org.w3c.dom.Text import java.io.File import java.text.SimpleDateFormat import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.* import kotlin.system.exitProcess class SecondScreenPresentation(context: Context, display: Display) : Presentation(context, display) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //setContentView(textView) } public fun setDisplayView(view : View) { setContentView(view) } } class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var defaultLayout: RelativeLayout private lateinit var videoLayout: RelativeLayout //private lateinit var videoView: VideoView private lateinit var callTitle: TextView private lateinit var callTimer: Chronometer private lateinit var callUri: AutoCompleteTextView private lateinit var securityButton: ImageButton private lateinit var videoSecurityButton: ImageButton private lateinit var diverter: LinearLayout private lateinit var diverterUri: TextView private lateinit var callButton: ImageButton private lateinit var callVideoButton: ImageButton private lateinit var hangupButton: ImageButton private lateinit var answerButton: ImageButton private lateinit var answerVideoButton: ImageButton private lateinit var rejectButton: ImageButton private lateinit var callControl: RelativeLayout private lateinit var holdButton: ImageButton private lateinit var transferButton: ImageButton private lateinit var videoButton: ImageButton private lateinit var voicemailButton: ImageButton private lateinit var voicemailButtonSpace: Space private lateinit var contactsButton: ImageButton private lateinit var messagesButton: ImageButton private lateinit var callsButton: ImageButton private lateinit var dialpadButton: ImageButton private lateinit var dtmf: EditText private var dtmfWatcher: TextWatcher? = null private lateinit var infoButton: ImageButton private lateinit var onHoldNotice: TextView private lateinit var videoOnHoldNotice: TextView private lateinit var uaAdapter: UaSpinnerAdapter private lateinit var aorSpinner: Spinner private lateinit var imm: InputMethodManager private lateinit var nm: NotificationManager private lateinit var am: AudioManager private lateinit var kgm: KeyguardManager private lateinit var screenEventReceiver: BroadcastReceiver private lateinit var serviceEventObserver: Observer> private var recIcon: MenuItem? = null private var micIcon: MenuItem? = null private var speakerIcon: MenuItem? = null private lateinit var speakerButton: ImageButton private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var requestPermissionLauncher: ActivityResultLauncher private lateinit var requestPermissionsLauncher: ActivityResultLauncher> private lateinit var accountsRequest: ActivityResultLauncher private lateinit var chatRequests: ActivityResultLauncher private lateinit var configRequest: ActivityResultLauncher private lateinit var backupRequest: ActivityResultLauncher private lateinit var restoreRequest: ActivityResultLauncher private lateinit var logcatRequest: ActivityResultLauncher private lateinit var contactsRequest: ActivityResultLauncher private lateinit var callsRequest: ActivityResultLauncher private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener private lateinit var permissions: Array private var callHandler: Handler = Handler(Looper.getMainLooper()) private var callRunnable: Runnable? = null private var downloadsInputUri: Uri? = null private var downloadsOutputUri: Uri? = null private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null private var presentation: Presentation? = null private lateinit var baresipService: Intent private var restart = false private var atStartup = false private var alerting = false private var resumeUri = "" private var resumeUap = 0L private var resumeCall: Call? = null private var resumeAction = "" private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { moveTaskToBack(true) } } @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { // theme.applyStyle(R.style.OptOutEdgeToEdgeEnforcement,false) super.onCreate(savedInstanceState) enableEdgeToEdge() AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) onBackPressedDispatcher.addCallback(this, onBackPressedCallback) binding = ActivityMainBinding.inflate(layoutInflater) val extraAction = intent.getStringExtra("action") Log.d(TAG, "Main onCreate ${intent.action}/${intent.data}/$extraAction") window.addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) // 시스템 바 숨김 (Full Screen Mode) - by ritoseo window.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ) setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v: View, insets: WindowInsetsCompat -> val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) WindowInsetsCompat.CONSUMED } // Must be done after view has been created this.setShowWhenLocked(true) this.setTurnScreenOn( true) Utils.requestDismissKeyguard(this) setSupportActionBar(binding.toolbar) supportActionBar?.title = "OSVC-C220" defaultLayout = binding.defaultLayout videoLayout = binding.videoLayout videoView = VideoView(applicationContext) aorSpinner = binding.aorSpinner callTitle = binding.callTitle callTimer = binding.callTimer callUri = binding.callUri securityButton = binding.securityButton diverter = binding.diverter diverterUri = binding.diverterUri callButton = binding.callButton callVideoButton = binding.callVideoButton hangupButton = binding.hangupButton answerButton = binding.answerButton answerVideoButton = binding.answerVideoButton rejectButton = binding.rejectButton callControl = binding.callControl holdButton = binding.holdButton transferButton = binding.transferButton dtmf = binding.dtmf infoButton = binding.info onHoldNotice = binding.onHoldNotice voicemailButton = binding.voicemailButton voicemailButtonSpace = binding.voicemailButtonSpace videoButton = binding.videoButton contactsButton = binding.contactsButton messagesButton = binding.messagesButton callsButton = binding.callsButton dialpadButton = binding.dialpadButton swipeRefresh = binding.swipeRefresh BaresipService.supportedCameras = Utils.supportedCameras(applicationContext).isNotEmpty() imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager am = getSystemService(AUDIO_SERVICE) as AudioManager kgm = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager serviceEventObserver = Observer { val event = it.getContentIfNotHandled() Log.d(TAG, "Observed event $event") if (event != null && BaresipService.serviceEvents.isNotEmpty()) { val first = BaresipService.serviceEvents.removeAt(0) handleServiceEvent(first.event, first.params) } } BaresipService.serviceEvent.observeForever(serviceEventObserver) screenEventReceiver = object : BroadcastReceiver() { override fun onReceive(contxt: Context, intent: Intent) { if (kgm.isKeyguardLocked) { Log.d(TAG, "Screen on when locked") this@MainActivity.setShowWhenLocked(Call.inCall()) } } } if(Utils.propertyGet("sys.rito.debug") == "1") { binding.toolbar.visibility = View.VISIBLE findViewById(R.id.header_bar).visibility = View.GONE } this.registerReceiver(screenEventReceiver, IntentFilter().apply { addAction(Intent.ACTION_SCREEN_ON) }) if (Build.VERSION.SDK_INT >= 31) { comDevChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device -> if (device != null) { Log.d(TAG, "Com device changed to type ${device.type} in mode ${am.mode}") setSpeakerButtonAndIcon() } } am.addOnCommunicationDeviceChangedListener(mainExecutor, comDevChangedListener) } uaAdapter = UaSpinnerAdapter(applicationContext, BaresipService.uas) aorSpinner.adapter = uaAdapter aorSpinner.setSelection(-1) aorSpinner.tag = "" aorSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { // Have to allow NULL view, since sometimes when onItemSelected is called, view is NULL. // Haven't found any explanation why this can happen. override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { Log.d(TAG, "aorSpinner selecting $position") if (position < BaresipService.uas.size) { val ua = BaresipService.uas[position] val acc = ua.account aorSpinner.tag = acc.aor showCall(ua) updateIcons(acc) } } override fun onNothingSelected(parent: AdapterView<*>) { Log.d(TAG, "Nothing selected") } } val registrationObserver = Observer { uaAdapter.notifyDataSetChanged() } BaresipService.registrationUpdate.observe(this, registrationObserver) accountsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { uaAdapter.notifyDataSetChanged() spinToAor(activityAor) if (aorSpinner.tag != "") updateIcons(Account.ofAor(aorSpinner.tag.toString())!!) if (BaresipService.isServiceRunning) { baresipService.action = "Update Notification" startService(baresipService) } } accountRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) val ua = UserAgent.ofAor(activityAor)!! updateIcons(ua.account) if (it.resultCode == Activity.RESULT_OK) if (BaresipService.aorPasswords[activityAor] == NO_AUTH_PASS) askPassword(getString(R.string.authentication_password), ua) } aorSpinner.setOnTouchListener { view, event -> if (event.action == MotionEvent.ACTION_DOWN) { if (aorSpinner.selectedItemPosition == -1) { val i = Intent(this@MainActivity, AccountsActivity::class.java) val b = Bundle() b.putString("aor", "") i.putExtras(b) accountsRequest.launch(i) true } else { if ((event.x - view.left) < 100) { val i = Intent(this@MainActivity, AccountActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) accountRequest!!.launch(i) true } else { BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri = callUri.text.toString() false } } } else { // view.performClick() false } } aorSpinner.setOnLongClickListener { if (aorSpinner.selectedItemPosition != -1) { val ua = UserAgent.ofAor(aorSpinner.tag.toString()) if (ua != null) { val acc = ua.account if (Api.account_regint(acc.accp) > 0) { Api.account_set_regint(acc.accp, 0) Api.ua_unregister(ua.uap) } else { Api.account_set_regint(acc.accp, acc.configuredRegInt) Api.ua_register(ua.uap) } acc.regint = Api.account_regint(acc.accp) AccountsActivity.saveAccounts() } } true } callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) callUri.threshold = 2 callUri.setOnFocusChangeListener { view, b -> if (b) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } } callUri.setOnClickListener { view -> imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } securityButton.setOnClickListener { when (securityButton.tag) { R.drawable.unlocked -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.call_not_secure)) } R.drawable.locked_yellow -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.peer_not_verified)) } R.drawable.locked_green -> { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.info) setMessage(getString(R.string.call_is_secure)) setPositiveButton(getString(R.string.unverify)) { dialog, _ -> val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) { Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed") } else { securityButton.setImageResource(R.drawable.locked_yellow) securityButton.tag = R.drawable.locked_yellow } } dialog.dismiss() } setNeutralButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } } } callButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO))) makeCall("voice") else Toast.makeText(applicationContext, getString(R.string.no_calls), Toast.LENGTH_SHORT).show() } } callVideoButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO, CAMERA))) makeCall("video") else Toast.makeText( applicationContext, getString(R.string.no_video_calls), Toast.LENGTH_SHORT ).show() } } hangupButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (Build.VERSION.SDK_INT < 31) { if (callRunnable != null) { callHandler.removeCallbacks(callRunnable!!) callRunnable = null BaresipService.abandonAudioFocus(applicationContext) showCall(ua) return@setOnClickListener } } else { if (audioModeChangedListener != null) { am.removeOnModeChangedListener(audioModeChangedListener!!) audioModeChangedListener = null BaresipService.abandonAudioFocus(applicationContext) showCall(ua) return@setOnClickListener } } val aor = ua.account.aor val uaCalls = ua.calls() if (uaCalls.size > 0) { val call = uaCalls[uaCalls.size - 1] val callp = call.callp Log.d(TAG, "AoR $aor hanging up call $callp with ${callUri.text}") hangupButton.isEnabled = false Api.ua_hangup(ua.uap, callp, 0, "") } } answerButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() ?: return@setOnClickListener Log.d(TAG, "AoR ${ua.account.aor} answering call from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false call.setMediaDirection(Api.SDP_SENDRECV, Api.SDP_INACTIVE) call.disableVideoStream(true) val intent = Intent(this@MainActivity, BaresipService::class.java) intent.action = "Call Answer" intent.putExtra("uap", ua.uap) intent.putExtra("callp", call.callp) startService(intent) } answerVideoButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.calls("in")[0] Log.d(TAG, "AoR $aor answering video call ${call.callp} from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false val videoDir = if (Utils.isCameraAvailable(this)) Api.SDP_SENDRECV else Api.SDP_RECVONLY call.setMediaDirection(Api.SDP_SENDRECV, videoDir) val intent = Intent(this@MainActivity, BaresipService::class.java) intent.action = "Call Answer" intent.putExtra("uap", ua.uap) intent.putExtra("callp", call.callp) startService(intent) } rejectButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.currentCall()!! val callp = call.callp Log.d(TAG, "AoR $aor rejecting call $callp from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false call.rejected = true Api.ua_hangup(ua.uap, callp, 486, "Busy Here") } holdButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.currentCall()!! if (call.onhold) { Log.d(TAG, "AoR $aor resuming call ${call.callp} with ${callUri.text}") call.resume() call.onhold = false holdButton.setImageResource(R.drawable.call_hold) } else { Log.d(TAG, "AoR $aor holding call ${call.callp} with ${callUri.text}") call.hold() call.onhold = true holdButton.setImageResource(R.drawable.resume) } } transferButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null ) { if (call.onHoldCall != null) { if (!call.executeTransfer()) Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.transfer_failed))) } else { makeTransfer(ua) } } } infoButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { val stats = call.stats("audio") if (stats != "") { val parts = stats.split(",") as ArrayList if (parts[2] == "0/0") { parts[2] = "?/?" parts[3] = "?/?" parts[4] = "?/?" } val codecs = call.audioCodecs() val duration = call.duration() val txCodec = codecs.split(',')[0].split("/") val rxCodec = codecs.split(',')[1].split("/") Utils.alertView(this, getString(R.string.call_info), "${String.format(getString(R.string.duration), duration)}\n" + "${getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/" + "${rxCodec[0]} ch ${rxCodec[2]}\n" + "${String.format(getString(R.string.rate), parts[0])}\n" + "${String.format(getString(R.string.average_rate), parts[1])}\n" + "${getString(R.string.packets)}: ${parts[2]}\n" + "${getString(R.string.lost)}: ${parts[3]}\n" + String.format(getString(R.string.jitter), parts[4])) } else { Utils.alertView(this, getString(R.string.call_info), getString(R.string.call_info_not_available)) } } } dtmf.setOnFocusChangeListener { view, hasFocus -> if (hasFocus) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } } dtmf.setOnClickListener { view -> imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } voicemailButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val acc = ua.account if (acc.vmUri != "") { val dialogClickListener = DialogInterface.OnClickListener { _, which -> when (which) { DialogInterface.BUTTON_POSITIVE -> { val i = Intent(this, MainActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP i.putExtra("action", "call") i.putExtra("uap", ua.uap) i.putExtra("peer", acc.vmUri) startActivity(i) } DialogInterface.BUTTON_NEGATIVE -> { } } } with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.voicemail_messages) setMessage(acc.vmMessages(this@MainActivity)) setPositiveButton(getString(R.string.listen), dialogClickListener) setNeutralButton(getString(R.string.cancel), dialogClickListener) show() } } } } contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) } contactsButton.setOnClickListener { val i = Intent(this@MainActivity, ContactsActivity::class.java) val b = Bundle() if (aorSpinner.selectedItemPosition >= 0) b.putString("aor", aorSpinner.tag.toString()) else b.putString("aor", "") i.putExtras(b) contactsRequest.launch(i) } chatRequests = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) updateIcons(Account.ofAor(activityAor)!!) } messagesButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, ChatsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) b.putString("peer", resumeUri) i.putExtras(b) chatRequests.launch(i) } } callsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) callsButton.setImageResource(R.drawable.calls) } callsButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, CallsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) callsRequest.launch(i) } } dialpadButton.tag = "off" dialpadButton.setOnClickListener { if (dialpadButton.tag == "off") { callUri.inputType = InputType.TYPE_CLASS_PHONE dialpadButton.setImageResource(R.drawable.dialpad_on) dialpadButton.tag = "on" //Log.d(TAG, "Screen ${Utils.getScreenOrientation(applicationContext)}") //val path = BaresipService.downloadsPath + "/video.mp4" //Utils.ffmpegExecute("-video_size hd720 -f android_camera -camera_index 1 -i anything -r 10 -t 5 -y $path") } else { callUri.inputType = InputType.TYPE_CLASS_TEXT + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS dialpadButton.setImageResource(R.drawable.dialpad_off) dialpadButton.tag = "off" } } videoButton.setOnClickListener { videoButton.isClickable = false videoButton.setImageResource(R.drawable.video_pending) Handler(Looper.getMainLooper()).postDelayed({ val call = Call.call("connected") if (call != null) { val dir = call.videoRequest if (dir != 0) { call.videoRequest = 0 call.setVideoDirection(dir) } else { if (Utils.isCameraAvailable(this)) call.setVideoDirection(Api.SDP_SENDRECV) else call.setVideoDirection(Api.SDP_RECVONLY) } imm.hideSoftInputFromWindow(dtmf.windowToken, 0) } }, 250) } configRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if ((it.data != null) && it.data!!.hasExtra("restart")) { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.restart_request) setMessage(getString(R.string.config_restart)) setPositiveButton(getText(R.string.restart)) { dialog, _ -> dialog.dismiss() quitRestart(true) } setNeutralButton(getText(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } val displayTheme = Preferences(applicationContext).displayTheme if (displayTheme != AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.setDefaultNightMode(displayTheme) delegate.applyDayNight() } } backupRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> downloadsOutputUri = uri askPassword(getString(R.string.encrypt_password)) } } restoreRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> downloadsInputUri = uri askPassword(getString(R.string.decrypt_password)) } } logcatRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> try { val out = contentResolver.openOutputStream(uri) val process = Runtime.getRuntime().exec("logcat -d --pid=${Process.myPid()}") val bufferedReader = process.inputStream.bufferedReader() bufferedReader.forEachLine { line -> out!!.write(line.toByteArray()) out.write('\n'.code.toByte().toInt()) } out!!.close() } catch (e: Exception) { Log.e(TAG, "Failed to write logcat to file") } } } swipeRefresh.setOnTouchListener(object : OnSwipeTouchListener(this@MainActivity) { override fun onSwipeLeft() { super.onSwipeLeft() if (BaresipService.uas.size > 0) { val curPos = aorSpinner.selectedItemPosition val newPos = if (curPos == -1) 0 else (curPos + 1) % BaresipService.uas.size if (curPos != newPos) { aorSpinner.setSelection(newPos) showCall(BaresipService.uas[newPos]) } } } override fun onSwipeRight() { super.onSwipeRight() if (BaresipService.uas.size > 0) { val curPos = aorSpinner.selectedItemPosition val newPos = when (curPos) { -1 -> 0 0 -> BaresipService.uas.size - 1 else -> curPos - 1 } if (curPos != newPos) { aorSpinner.setSelection(newPos) showCall(BaresipService.uas[newPos]) } } } }) swipeRefresh.setOnRefreshListener { if (BaresipService.uas.size > 0) { if (aorSpinner.selectedItemPosition == -1) aorSpinner.setSelection(0) val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (ua.account.regint > 0) Api.ua_register(ua.uap) } swipeRefresh.isRefreshing = false } baresipService = Intent(this@MainActivity, BaresipService::class.java) atStartup = intent.hasExtra("onStartup") when (intent?.action) { ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> if (BaresipService.isServiceRunning) callAction(intent.data, if (intent?.action == ACTION_CALL) "call" else "dial") else BaresipService.callActionUri = intent.data.toString() .replace("tel:%2B", "tel:+") } permissions = if (BaresipService.supportedCameras) { if (Build.VERSION.SDK_INT >= 33) arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT) else if (Build.VERSION.SDK_INT >= 31) arrayOf(RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT) else arrayOf(RECORD_AUDIO, CAMERA) } else { if (Build.VERSION.SDK_INT >= 33) arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, BLUETOOTH_CONNECT) else if (Build.VERSION.SDK_INT >= 31) arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT) else arrayOf(RECORD_AUDIO) } requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {} requestPermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { val denied = mutableListOf() val shouldShow = mutableListOf() it.forEach { permission -> if (!permission.value) { denied.add(permission.key) if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.key)) shouldShow.add(permission.key) } } if (denied.contains(POST_NOTIFICATIONS) && !shouldShow.contains(POST_NOTIFICATIONS)) { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(getString(R.string.notice)) setMessage(getString(R.string.no_notifications)) setPositiveButton(getString(R.string.ok)) { _, _ -> quitRestart(false) } show() } } else { if (shouldShow.isNotEmpty()) Utils.alertView(this, getString(R.string.permissions_rationale), if (CAMERA in permissions) getString(R.string.audio_and_video_permissions) else getString(R.string.audio_permissions) ) { requestPermissionsLauncher.launch(permissions) } else startBaresip() } } addVideoLayoutViews() if (!BaresipService.isServiceRunning) { if (File(filesDir.absolutePath + "/accounts").exists()) { val accounts = String( Utils.getFileContents(filesDir.absolutePath + "/accounts")!!, Charsets.UTF_8 ).lines().toMutableList() askPasswords(accounts) } else { // Baresip is started for the first time requestPermissionsLauncher.launch(permissions) } } } // OnCreate override fun onStart() { super.onStart() Log.d(TAG, "Main onStart") val action = intent.getStringExtra("action") if (action != null) { // MainActivity was not visible when call, message, or transfer request came in intent.removeExtra("action") handleIntent(intent, action) } } override fun onResume() { super.onResume() Log.d(TAG, "Main onResume with action '$resumeAction'") nm.cancelAll() BaresipService.isMainVisible = true when (resumeAction) { "call show" -> { handleServiceEvent ("call incoming", arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) } "call answer" -> { answerButton.performClick() showCall(resumeCall!!.ua) } "call missed" -> { callsButton.performClick() } "call reject" -> rejectButton.performClick() "call" -> { callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) callButton.performClick() } "dial" -> { callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) } "call transfer", "transfer show", "transfer accept" -> handleServiceEvent("$resumeAction,$resumeUri", arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) "message", "message show", "message reply" -> handleServiceEvent(resumeAction, arrayListOf(resumeUap, resumeUri)) else -> { val incomingCall = Call.call("incoming") if (incomingCall != null) { spinToAor(incomingCall.ua.account.aor) } else { restoreActivities() if (BaresipService.uas.size > 0) { if (aorSpinner.selectedItemPosition == -1) { if (Call.inCall()) spinToAor(Call.calls()[0].ua.account.aor) else { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } } } } uaAdapter.notifyDataSetChanged() if (BaresipService.uas.size > 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] showCall(ua) updateIcons(ua.account) } } } resumeAction = "" val tv = findViewById(R.id.textViewHeaderIp) tv.setText(BaresipService.deviceIpAddress) } override fun onPause() { super.onPause() Log.d(TAG, "Main onPause") Utils.addActivity("main") BaresipService.isMainVisible = false callTimer.stop() saveCallUri() } override fun onStop() { super.onStop() Log.d(TAG, "Main onStop") } override fun onDestroy() { super.onDestroy() Log.d(TAG, "Main onDestroy") this.unregisterReceiver(screenEventReceiver) if (Build.VERSION.SDK_INT >= 31) am.removeOnCommunicationDeviceChangedListener(comDevChangedListener) BaresipService.serviceEvent.removeObserver(serviceEventObserver) BaresipService.serviceEvents.clear() BaresipService.activities.clear() } var prevLayoutShow = false private fun switchVideoLayout(show : Boolean) { if(show == prevLayoutShow) { return } prevLayoutShow = show if(BaresipService.connectedDisplayCount < 2) return println("switchVideoLayout : ${show}") if(show) { // var prm: FrameLayout.LayoutParams = // FrameLayout.LayoutParams( // 1920, // 1080 // ) // prm.leftMargin = 0 // prm.topMargin = 0 // videoView.surfaceSelfView.layoutParams = prm // try { // val parentView = videoView.surfaceSelfView.parent // (parentView as ViewGroup).removeView(videoView.surfaceSelfView) // } catch(e : java.lang.Exception) { // } // presentation?.setContentView(videoView.surfaceSelfView) videoView.surfaceSelfView.bringToFront() } else { // try { // val parentView = videoView.surfaceSelfView.parent // (parentView as ViewGroup).removeView(videoView.surfaceSelfView) // } catch(e : java.lang.Exception) { // } // // var prm2: FrameLayout.LayoutParams = // FrameLayout.LayoutParams( // 1920, // 1080 // ) // prm2.leftMargin = 1919 // prm2.topMargin = 1079 // videoView.surfaceSelfView.layoutParams = prm2 // videoLayout.addView(videoView.surfaceSelfView) // presentation?.setContentView(videoView.standbyView) videoView.standbyView.bringToFront() } } private fun isCallExist() : Boolean { if(BaresipService.uas.size == 0) return false val ua = BaresipService.uas[0] val call = ua.currentCall() if (call != null) { return true } return false } private fun updateDisplayLayout() { if(BaresipService.connectedDisplayCount > 1) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 return } if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_최대_근거리_우하단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 1440 prm2.topMargin = 810 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_최대_근거리_우상단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 1440 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌상단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌하단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 0 prm2.topMargin = 810 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_대상단_근거리_소하단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1440, 810 ) prm1.leftMargin = 240 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 720 prm2.topMargin = 810 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_대좌단_근거리_소우단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1440, 810 ) prm1.leftMargin = 0 prm1.topMargin = 135 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 1440 prm2.topMargin = 135 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_대하단_근거리_소상단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1440, 810 ) prm1.leftMargin = 240 prm1.topMargin = 270 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 720 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 } else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_반좌단_근거리_반우단) { var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 960, 540 ) prm1.leftMargin = 0 prm1.topMargin = 270 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 960, 540 ) prm2.leftMargin = 960 prm2.topMargin = 270 videoView.surfaceSelfView.layoutParams = prm2 } } private fun updateInfo() { val view = findViewById(R.id.textViewDeviceName) view.setText(BaresipService.deviceName) val now = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일(E요일) a h시 m분", Locale.KOREAN) val viewDate = findViewById(R.id.textViewDatetime) val formattedDate = now.format(formatter) viewDate.setText(formattedDate) } private fun updateDisplay() { println("Update Display : ${BaresipService.connectedDisplayCount}") if(BaresipService.connectedDisplayCount < 2) { try { val parentView = videoView.surfaceSelfView.parent (parentView as ViewGroup).removeView(videoView.surfaceSelfView) } catch(e : java.lang.Exception) { } var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1, 1 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 videoLayout.addView(videoView.surfaceSelfView) updateDisplayLayout() } else { try { val parentView = videoView.surfaceSelfView.parent (parentView as ViewGroup).removeView(videoView.surfaceSelfView) } catch(e : java.lang.Exception) { } try { val parentView = videoView.standbyView.parent (parentView as ViewGroup).removeView(videoView.standbyView) } catch(e : java.lang.Exception) { } val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager val displays = displayManager.displays val secondDisplay = displays[1] // 두 번째 디스플레이 가져오기 presentation = SecondScreenPresentation(this, secondDisplay) var prm1: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm1.leftMargin = 0 prm1.topMargin = 0 videoView.surfaceView.layoutParams = prm1 var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 presentation?.setContentView(R.layout.presentation_layout) val presentationView = presentation?.window?.decorView?.findViewById(R.id.presentationLayout) try { presentationView?.addView(videoView.surfaceSelfView) } catch(e : java.lang.Exception) { } try { presentationView?.addView(videoView.standbyView) } catch(e : java.lang.Exception) { } videoView.standbyView.bringToFront() presentation?.show() } } private fun addVideoLayoutViews() { var connectedCount = 0 var useSplit = false for(idx in 0..1) { if (Utils.checkDisplayConnection(idx) == "connected") connectedCount++ } BaresipService.connectedDisplayCount = connectedCount if(connectedCount < 2) useSplit = true //videoLayout.addView(videoView.surfaceView) if(useSplit) { var prm: FrameLayout.LayoutParams = FrameLayout.LayoutParams( 1920, 1080 ) prm.leftMargin = 0 prm.topMargin = 0 videoView.surfaceView.layoutParams = prm videoLayout.addView(videoView.surfaceView) //videoView.surfaceSelfView.bringToFront() var prm2: FrameLayout.LayoutParams = FrameLayout.LayoutParams( 480, 270 ) prm2.leftMargin = 1440 prm2.topMargin = 810 videoView.surfaceSelfView.layoutParams = prm2 videoLayout.addView(videoView.surfaceSelfView) } else { val useSecondScreenAsFar = false; // DisplayManager 가져오기 val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager val displays = displayManager.displays if (displays.size > 1) { // 보조 디스플레이가 있는 경우 println("디스플레이 상태 : [0]${Utils.checkDisplayConnection(0)} [1]${Utils.checkDisplayConnection(1)}") val secondDisplay = displays[1] // 두 번째 디스플레이 가져오기 presentation = SecondScreenPresentation(this, secondDisplay) if(useSecondScreenAsFar) { presentation?.setContentView(videoView.surfaceView) } else { //presentation?.setContentView(videoView.surfaceSelfView) var prm2: FrameLayout.LayoutParams = FrameLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 //videoLayout.addView(videoView.surfaceSelfView) presentation?.setContentView(R.layout.presentation_layout) val presentationView = presentation?.window?.decorView?.findViewById(R.id.presentationLayout) presentationView?.addView(videoView.surfaceSelfView) presentationView?.addView(videoView.standbyView) //presentation?.setContentView(videoView.standbyView) } presentation?.show() } else { var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 1919 prm2.topMargin = 1079 videoView.surfaceSelfView.layoutParams = prm2 videoLayout.addView(videoView.surfaceSelfView) //videoView.surfaceSelfView.visibility = View.INVISIBLE } if(useSecondScreenAsFar) { videoLayout.addView(videoView.surfaceSelfView) } else { videoLayout.addView(videoView.surfaceView) } } // Video Button val vb = ImageButton(this) vb.id = View.generateViewId() vb.setImageResource(R.drawable.video_off) vb.setBackgroundResource(0) var prm: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) prm.marginStart = 15 prm.bottomMargin = 15 vb.layoutParams = prm vb.setOnClickListener { Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE) } videoLayout.addView(vb) vb.visibility = View.INVISIBLE // by ritoseo // Camera Button val cb = ImageButton(this) if (!Utils.isCameraAvailable(this)) cb.visibility = View.INVISIBLE cb.visibility = View.INVISIBLE // by ritoseo cb.id = View.generateViewId() cb.setImageResource(R.drawable.camera_front) cb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT ) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ALIGN_PARENT_TOP) prm.marginStart = 15 prm.topMargin = 15 cb.layoutParams = prm cb.setOnClickListener { val call = Call.call("connected") if (call != null) { if (call.setVideoSource(!BaresipService.cameraFront) != 0) Log.w(TAG, "Failed to set video source") else BaresipService.cameraFront = !BaresipService.cameraFront if (BaresipService.cameraFront) cb.setImageResource(R.drawable.camera_front) else cb.setImageResource(R.drawable.camera_rear) } } videoLayout.addView(cb) // Snapshot Button if ((Build.VERSION.SDK_INT >= 29) || Utils.checkPermissions(this, arrayOf(WRITE_EXTERNAL_STORAGE))) { val sb = ImageButton(this) sb.setImageResource(R.drawable.snapshot) sb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.BELOW, cb.id) prm.marginStart = 15 prm.topMargin= 24 sb.layoutParams = prm sb.setOnClickListener { val sdf = SimpleDateFormat("yyyyMMdd_hhmmss", Locale.getDefault()) val fileName = "IMG_" + sdf.format(Date()) + ".png" val filePath = BaresipService.filesPath + "/" + fileName if (Api.cmd_exec("snapshot_recv $filePath") != 0) Log.e(TAG, "Command 'snapshot_recv $filePath' failed") else MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) } videoLayout.addView(sb) sb.visibility = View.INVISIBLE // by ritoseo } // Lock Button videoSecurityButton = ImageButton(this) videoSecurityButton.visibility = View.INVISIBLE videoSecurityButton.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ABOVE, vb.id) prm.marginStart = 15 prm.bottomMargin= 24 videoSecurityButton.layoutParams = prm videoSecurityButton.setOnClickListener { when (securityButton.tag) { R.drawable.unlocked -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.call_not_secure)) } R.drawable.locked_yellow -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.peer_not_verified)) } R.drawable.locked_green -> { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.info) setMessage(getString(R.string.call_is_secure)) setPositiveButton(getString(R.string.unverify)) { dialog, _ -> val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) { Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed") } else { securityButton.tag = R.drawable.locked_yellow videoSecurityButton.setImageResource(R.drawable.locked_video_yellow) } } dialog.dismiss() } setNeutralButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } } } videoLayout.addView(videoSecurityButton) videoSecurityButton.visibility = View.INVISIBLE // by ritoseo // Speaker Button speakerButton = ImageButton(this) speakerButton.id = View.generateViewId() speakerButton.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.ALIGN_PARENT_TOP) prm.marginEnd = 15 prm.topMargin = 15 speakerButton.layoutParams = prm speakerButton.setOnClickListener { Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am) setSpeakerButtonAndIcon() } setSpeakerButtonAndIcon() videoLayout.addView(speakerButton) speakerButton.visibility = View.INVISIBLE // by ritoseo // Mic Button val mb = ImageButton(this) mb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.BELOW, speakerButton.id) prm.marginEnd = 15 prm.topMargin= 24 mb.layoutParams = prm if (BaresipService.isMicMuted) mb.setImageResource(R.drawable.mic_off_button) else mb.setImageResource(R.drawable.mic_on_button) mb.setOnClickListener { BaresipService.isMicMuted = !BaresipService.isMicMuted if (BaresipService.isMicMuted) { mb.setImageResource(R.drawable.mic_off_button) Api.calls_mute(true) } else { mb.setImageResource(R.drawable.mic_on_button) Api.calls_mute(false) } } videoLayout.addView(mb) mb.visibility = View.INVISIBLE // by ritoseo // Hangup Button val hb = ImageButton(this) hb.id = View.generateViewId() hb.setImageResource(R.drawable.hangup) hb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) prm.marginEnd = 15 prm.bottomMargin = 15 hb.layoutParams = prm hb.setOnClickListener { if (!Utils.isCameraAvailable(this)) Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE) hangupButton.performClick() } videoLayout.addView(hb) // Info Button val ib = ImageButton(this) ib.setImageResource(R.drawable.video_info) ib.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.ABOVE, hb.id) prm.marginEnd = 15 prm.bottomMargin= 24 ib.layoutParams = prm ib.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val calls = ua.calls() if (calls.size > 0) { val call = calls[0] val stats = call.stats("video") if (stats != "") { val parts = stats.split(",") val codecs = call.videoCodecs().split(',') val duration = call.duration() val txCodec = codecs[0] val rxCodec = codecs[1] Utils.alertView(this, getString(R.string.call_info), "${String.format(getString(R.string.duration), duration)}\n" + "${getString(R.string.codecs)}: $txCodec/$rxCodec\n" + "${String.format(getString(R.string.rate), parts[0])}\n" + "${String.format(getString(R.string.average_rate), parts[1])}\n" + "${String.format(getString(R.string.jitter), parts[4])}\n" + "${getString(R.string.packets)}: ${parts[2]}\n" + "${getString(R.string.lost)}: ${parts[3]}") } else { Utils.alertView(this, getString(R.string.call_info), getString(R.string.call_info_not_available)) } } } videoLayout.addView(ib) ib.visibility = View.INVISIBLE // by ritoseo // OnHold Notice videoOnHoldNotice = TextView(this) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE) prm.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE) videoOnHoldNotice.layoutParams = prm videoOnHoldNotice.text = getString(R.string.call_is_on_hold) videoOnHoldNotice.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) videoOnHoldNotice.setTextColor(ContextCompat.getColor(this, R.color.colorAccent)) videoOnHoldNotice.visibility = View.GONE videoLayout.addView(videoOnHoldNotice) videoOnHoldNotice.visibility = View.INVISIBLE // by ritoseo } private fun setSpeakerButtonAndIcon() { if (Utils.isSpeakerPhoneOn(am)) { speakerButton.setImageResource(R.drawable.speaker_on_button) if (speakerIcon != null) speakerIcon!!.setIcon(R.drawable.speaker_on) } else { speakerButton.setImageResource(R.drawable.speaker_off_button) if (speakerIcon != null) speakerIcon!!.setIcon(R.drawable.speaker_off) } } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (dtmf.isEnabled) dtmf.requestFocus() } override fun onNewIntent(intent: Intent) { // Called when MainActivity already exists at the top of current task super.onNewIntent(intent) this.setShowWhenLocked(true) this.setTurnScreenOn(true) resumeAction = "" resumeUri = "" Log.d(TAG, "onNewIntent with action/data '${intent.action}/${intent.data}'") when (intent.action) { ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> { callAction(intent.data, if (intent.action == ACTION_CALL) "call" else "dial") } else -> { val action = intent.getStringExtra("action") if (action != null) { intent.removeExtra("action") handleIntent(intent, action) } } } } private fun callAction(uri: Uri?, action: String) { if (Call.inCall() || BaresipService.uas.size == 0) return Log.d(TAG, "Action $action to $uri") if (uri != null) { when (uri.scheme) { "sip" -> { val uriStr = Utils.uriUnescape(uri.toString()) var ua = UserAgent.ofDomain(Utils.uriHostPart(uriStr)) if (ua == null && BaresipService.uas.size > 0) ua = BaresipService.uas[0] if (ua == null) { Log.w(TAG, "No accounts for '$uriStr'") return } spinToAor(ua.account.aor) resumeAction = action ua.account.resumeUri = uriStr } "tel" -> { val uriStr = uri.toString().replace("%2B", "+") .replace("%20", "") .filterNot{setOf('-', ' ', '(', ')').contains(it)} var account: Account? = null for (a in Account.accounts()) if (a.telProvider != "") { account = a break } if (account == null) { Log.w(TAG, "No telephony providers for '$uriStr'") return } spinToAor(account.aor) resumeAction = action account.resumeUri = uriStr } else -> { Log.w(TAG, "Unsupported URI scheme ${uri.scheme}") return } } } } private fun handleIntent(intent: Intent, action: String) { Log.d(TAG, "Handling intent '$action'") val ev = action.split(",") when (ev[0]) { "no network" -> { // 네트워크 안잡힌 경우 /* Utils.alertView(this, getString(R.string.notice), getString(R.string.no_network)) */ return } /* Added by ritoseo */ "network available" -> { val tv = findViewById(R.id.textViewHeaderIp) tv.setText(intent.getStringExtra("address")!!) } "video call" -> { callUri.setText(intent.getStringExtra("peer")!!) println("resumeUri : ${callUri.text}") makeCall("video", false) } "hangup" -> { hangupButton.performClick() } "update display" -> { updateDisplay() } "update layout" -> { updateDisplayLayout() } "update info" -> { updateInfo() } /********************/ "call", "dial" -> { if (Call.inCall()) { Toast.makeText(applicationContext, getString(R.string.call_already_active), Toast.LENGTH_SHORT).show() return } val uap = intent.getLongExtra("uap", 0L) val ua = UserAgent.ofUap(uap) if (ua == null) { Log.w(TAG, "handleIntent 'call' did not find ua $uap") return } if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) resumeAction = action ua.account.resumeUri = intent.getStringExtra("peer")!! } "call show", "call answer" -> { val callp = intent.getLongExtra("callp", 0L) val call = Call.ofCallp(callp) if (call == null) { Log.w(TAG, "handleIntent '$action' did not find call $callp") return } val ua = call.ua if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) resumeAction = action resumeCall = call if(ev[0] == "call answer") { videoButton.performClick() } } "call missed" -> { val uap = intent.getLongExtra("uap", 0L) val ua = UserAgent.ofUap(uap) if (ua == null) { Log.w(TAG, "handleIntent did not find ua $uap") return } if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) resumeAction = action } "call transfer", "transfer show", "transfer accept" -> { val callp = intent.getLongExtra("callp", 0L) val call = Call.ofCallp(callp) if (call == null) { Log.w(TAG, "handleIntent '$action' did not find call $callp") moveTaskToBack(true) return } resumeAction = ev[0] resumeCall = call resumeUri = if (ev[0] == "call transfer") ev[1] else intent.getStringExtra("uri")!! } "message", "message show", "message reply" -> { val uap = intent.getLongExtra("uap", 0L) val ua = UserAgent.ofUap(uap) if (ua == null) { Log.w(TAG, "handleIntent did not find ua $uap") return } if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) resumeAction = action resumeUap = uap resumeUri = intent.getStringExtra("peer")!! } } } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { val BTN_MISC = 188 //println("onKeyDown : ${keyCode}") val stream = if (am.mode == AudioManager.MODE_RINGTONE) AudioManager.STREAM_RING else AudioManager.STREAM_MUSIC when (keyCode) { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> { am.adjustStreamVolume(stream, if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) AudioManager.ADJUST_LOWER else AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI) Log.d(TAG, "Adjusted volume $keyCode of stream $stream to ${am.getStreamVolume(stream)}") return true } KeyEvent.KEYCODE_BACK -> { callVideoButton.requestFocus() return true } BTN_MISC -> { return true } } return super.onKeyDown(keyCode, event) } private fun handleServiceEvent(event: String, params: ArrayList) { fun handleNextEvent(logMessage: String? = null) { if (logMessage != null) Log.w(TAG, logMessage) if (BaresipService.serviceEvents.isNotEmpty()) { val first = BaresipService.serviceEvents.removeAt(0) handleServiceEvent(first.event, first.params) } } if (taskId == -1) { handleNextEvent("Omit service event '$event' for task -1") return } if (event == "started") { val uriString = params[0] as String Log.d(TAG, "Handling service event 'started' with URI '$uriString'") if (!this::uaAdapter.isInitialized) { // Android has restarted baresip when permission has been denied in app settings recreate() return } uaAdapter.notifyDataSetChanged() if (uriString != "") { callAction(uriString.toUri(), "dial") } else { if ((aorSpinner.selectedItemPosition == -1) && (BaresipService.uas.size > 0)) { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } } if (Preferences(applicationContext).displayTheme != AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.setDefaultNightMode(Preferences(applicationContext).displayTheme) delegate.applyDayNight() } handleNextEvent() return } if (event == "stopped") { Log.d(TAG, "Handling service event 'stopped' with start error '${params[0]}'") if (params[0] != "") { Utils.alertView(this, getString(R.string.notice), getString(R.string.start_failed)) } else { finishAndRemoveTask() if (restart) reStart() else exitProcess(0) } return } val uap = params[0] as Long val ua = UserAgent.ofUap(uap) if (ua == null) { handleNextEvent("handleServiceEvent '$event' did not find ua $uap") return } val ev = event.split(",") Log.d(TAG, "Handling service event '${ev[0]}' for $uap") val acc = ua.account val aor = ua.account.aor when (ev[0]) { "call rejected" -> { if (aor == aorSpinner.tag) { callsButton.setImageResource(R.drawable.calls_missed) } } "call incoming", "call outgoing" -> { val callp = params[1] as Long if (BaresipService.isMainVisible) { uaAdapter.notifyDataSetChanged() if (aor != aorSpinner.tag) spinToAor(aor) showCall(ua, Call.ofCallp(callp)) } else { Log.d(TAG, "Reordering to front") BaresipService.activities.clear() BaresipService.serviceEvents.clear() val i = Intent(applicationContext, MainActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP i.putExtra("action", "call show") i.putExtra("callp", callp) startActivity(i) return } } "call answered" -> { showCall(ua) } "call redirect", "video call redirect" -> { val redirectUri = ev[1] val target = Utils.friendlyUri(this, redirectUri, acc) if (acc.autoRedirect) { redirect(ev[0], ua, redirectUri) Toast.makeText(applicationContext, String.format(getString(R.string.redirect_notice), target), Toast.LENGTH_SHORT ).show() } else { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.redirect_request) setMessage(String.format(getString(R.string.redirect_request_query), target)) setPositiveButton(getString(R.string.yes)) { dialog, _ -> redirect(ev[0], ua, redirectUri) dialog.dismiss() } setNeutralButton(getString(R.string.no)) { dialog, _ -> dialog.dismiss() } show() } } showCall(ua) } "call established" -> { if (aor == aorSpinner.tag) { dtmf.text = null dtmf.hint = getString(R.string.dtmf) showCall(ua) } } "call update" -> { showCall(ua) } "call video request" -> { val callp = params[1] as Long val dir = params[2] as Int val call = Call.ofCallp(callp) if (call == null) { Log.w(TAG, "Video request call $callp not found") return } if (!isFinishing && !alerting) { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.video_request) val peerUri = Utils.friendlyUri(this@MainActivity, call.peerUri, acc) val msg = when (dir) { 1 -> String.format(getString(R.string.allow_video_recv), peerUri) 2 -> String.format(getString(R.string.allow_video_send), peerUri) 3 -> String.format(getString(R.string.allow_video), peerUri) else -> "" } setMessage(msg) setPositiveButton(getString(R.string.yes)) { dialog, _ -> call.videoRequest = dir videoButton.performClick() dialog.dismiss() alerting = false } setNeutralButton(getString(R.string.no)) { dialog, _ -> dialog.dismiss() alerting = false } alerting = true show() } } } "call verify" -> { val callp = params[1] as Long val call = Call.ofCallp(callp) if (call == null) { handleNextEvent("Call $callp to be verified is not found") return } with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.verify) setMessage(String.format(getString(R.string.verify_sas), ev[1])) setPositiveButton(getString(R.string.yes)) { dialog, _ -> call.security = if (Api.cmd_exec("zrtp_verify ${ev[2]}") != 0) { Log.e(TAG, "Command 'zrtp_verify ${ev[2]}' failed") R.drawable.locked_yellow } else { R.drawable.locked_green } call.zid = ev[2] if (aor == aorSpinner.tag) { securityButton.tag = call.security securityButton.setImageResource(call.security) setVideoSecurityButton(call.security) } dialog.dismiss() } setNeutralButton(getString(R.string.no)) { dialog, _ -> call.security = R.drawable.locked_yellow call.zid = ev[2] if (aor == aorSpinner.tag) { securityButton.tag = R.drawable.locked_yellow securityButton.setImageResource(R.drawable.locked_yellow) setVideoSecurityButton(R.drawable.locked_yellow) } dialog.dismiss() } show() } } "call verified", "call secure" -> { val callp = params[1] as Long val call = Call.ofCallp(callp) if (call == null) { handleNextEvent("Call $callp that is verified is not found") return } if (aor == aorSpinner.tag) { securityButton.setImageResource(call.security) securityButton.tag = call.security setVideoSecurityButton(call.security) } } "call transfer", "transfer show" -> { val callp = params[1] as Long if (!BaresipService.isMainVisible) { Log.d(TAG, "Reordering to front") BaresipService.activities.clear() BaresipService.serviceEvents.clear() val i = Intent(applicationContext, MainActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP i.putExtra("action", event) i.putExtra("callp", callp) startActivity(i) return } val call = Call.ofCallp(callp)!! val target = Utils.friendlyUri(this, ev[1], acc) with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.transfer_request) setMessage(String.format(getString(R.string.transfer_request_query), target)) setPositiveButton(getString(R.string.yes)) { dialog, _ -> acceptTransfer(ua, call, ev[1]) dialog.dismiss() } setNeutralButton(getString(R.string.no)) { dialog, _ -> if (call in Call.calls()) call.notifySipfrag(603, "Decline") dialog.dismiss() } show() } } "transfer accept" -> { val callp = params[1] as Long val call = Call.ofCallp(callp) if (call in Call.calls()) Api.ua_hangup(uap, callp, 0, "") call(ua, ev[1], "voice") showCall(ua) } "transfer failed" -> { showCall(ua) } "call closed" -> { uaAdapter.notifyDataSetChanged() val call = ua.currentCall() if (call != null) { call.resume() startCallTimer(call) } else { callTimer.stop() } if (aor == aorSpinner.tag) { callUri.inputType = InputType.TYPE_CLASS_TEXT + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS dialpadButton.setImageResource(R.drawable.dialpad_off) dialpadButton.tag = "off" ua.account.resumeUri = "" showCall(ua) if (acc.missedCalls) callsButton.setImageResource(R.drawable.calls_missed) } if (kgm.isDeviceLocked) this.setShowWhenLocked(false) switchVideoLayout(false) callVideoButton.requestFocus() } "message", "message show", "message reply" -> { val i = Intent(applicationContext, ChatActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val b = Bundle() b.putString("aor", aor) b.putString("peer", params[1] as String) b.putBoolean("focus", ev[0] == "message reply") i.putExtras(b) chatRequests.launch(i) } "mwi notify" -> { val lines = ev[1].split("\n") for (line in lines) { if (line.startsWith("Voice-Message:")) { val counts = (line.split(" ")[1]).split("/") acc.vmNew = counts[0].toInt() acc.vmOld = counts[1].toInt() break } } if (aor == aorSpinner.tag) { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) else voicemailButton.setImageResource(R.drawable.voicemail) } } else -> Log.e(TAG, "Unknown event '${ev[0]}'") } handleNextEvent() } private fun redirect(event: String, ua: UserAgent, redirectUri: String) { if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) callUri.setText(redirectUri) if (event == "call redirect") callButton.performClick() else callVideoButton.performClick() } private fun reStart() { Log.d(TAG, "Trigger restart") val pm = applicationContext.packageManager val intent = pm.getLaunchIntentForPackage(this.packageName) this.startActivity(intent) exitProcess(0) } override fun onPrepareOptionsMenu(menu: Menu): Boolean { if(Utils.propertyGet("sys.rito.debug") == "1") { if (Build.VERSION.SDK_INT >= 29) menu.findItem(R.id.logcat).setVisible(true) } return super.onPrepareOptionsMenu(menu) } override fun onCreateOptionsMenu(menu: Menu): Boolean { if(Utils.propertyGet("sys.rito.debug") == "1") { menuInflater.inflate(R.menu.main_menu, menu) } // main layout 툴바 상단 아이콘 숨김 - by ritoseo /* menuInflater.inflate(R.menu.rec_icon, menu) recIcon = menu.findItem(R.id.recIcon) if (BaresipService.isRecOn) recIcon!!.setIcon(R.drawable.rec_on) else recIcon!!.setIcon(R.drawable.rec_off) menuInflater.inflate(R.menu.mic_icon, menu) micIcon = menu.findItem(R.id.micIcon) if (BaresipService.isMicMuted) micIcon!!.setIcon(R.drawable.mic_off) else micIcon!!.setIcon(R.drawable.mic_on) menuInflater.inflate(R.menu.speaker_icon, menu) speakerIcon = menu.findItem(R.id.speakerIcon) if (Utils.isSpeakerPhoneOn(am)) speakerIcon!!.setIcon(R.drawable.speaker_on) else speakerIcon!!.setIcon(R.drawable.speaker_off) */ return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.recIcon -> { if (Call.call("connected") == null) { BaresipService.isRecOn = !BaresipService.isRecOn if (BaresipService.isRecOn) { item.setIcon(R.drawable.rec_on) Api.module_load("sndfile") } else { item.setIcon(R.drawable.rec_off) Api.module_unload("sndfile") } } else { Toast.makeText( applicationContext, R.string.rec_in_call, Toast.LENGTH_SHORT ).show() } } R.id.micIcon -> { if (Call.call("connected") != null) { BaresipService.isMicMuted = !BaresipService.isMicMuted if (BaresipService.isMicMuted) { item.setIcon(R.drawable.mic_off) Api.calls_mute(true) } else { item.setIcon(R.drawable.mic_on) Api.calls_mute(false) } } } R.id.speakerIcon -> { if (Build.VERSION.SDK_INT >= 31) Log.d(TAG, "Toggling speakerphone when dev/mode is " + "${am.communicationDevice!!.type}/${am.mode}") Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am) setSpeakerButtonAndIcon() } R.id.config -> { configRequest.launch(Intent(this, ConfigActivity::class.java)) } R.id.accounts -> { val i = Intent(this, AccountsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) accountsRequest.launch(i) } R.id.backup -> { when { Build.VERSION.SDK_INT >= 29 -> pickupFileFromDownloads("backup") ContextCompat.checkSelfPermission( this, WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED -> { Log.d(TAG, "Write External Storage permission granted") val path = Utils.downloadsPath("baresip+.bs") downloadsOutputUri = File(path).toUri() askPassword(getString(R.string.encrypt_password)) } ActivityCompat.shouldShowRequestPermissionRationale( this, WRITE_EXTERNAL_STORAGE) -> { defaultLayout.showSnackBar( binding.root, getString(R.string.no_backup), Snackbar.LENGTH_INDEFINITE, getString(R.string.ok) ) { requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } } else -> { requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } } } R.id.restore -> { when { Build.VERSION.SDK_INT >= 29 -> pickupFileFromDownloads("restore") ContextCompat.checkSelfPermission( this, READ_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED -> { Log.d(TAG, "Read External Storage permission granted") val path = Utils.downloadsPath("baresip+.bs") downloadsInputUri = File(path).toUri() askPassword(getString(R.string.decrypt_password)) } ActivityCompat.shouldShowRequestPermissionRationale( this, READ_EXTERNAL_STORAGE) -> { defaultLayout.showSnackBar( binding.root, getString(R.string.no_restore), Snackbar.LENGTH_INDEFINITE, getString(R.string.ok) ) { requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } } else -> { requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } } } R.id.logcat -> { if (Build.VERSION.SDK_INT >= 29) pickupFileFromDownloads("logcat") } R.id.about -> { startActivity(Intent(this, AboutActivity::class.java)) } R.id.restart, R.id.quit -> { quitRestart(item.itemId == R.id.restart) } } return true } private fun quitRestart(reStart: Boolean) { Log.d(TAG, "quitRestart Restart = $reStart") window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) if (BaresipService.isServiceRunning) { restart = reStart baresipService.action = "Stop" startService(baresipService) } else { finishAndRemoveTask() if (reStart) reStart() else exitProcess(0) } } private fun makeTransfer(ua: UserAgent) { val layout = LayoutInflater.from(this) .inflate(R.layout.call_transfer_dialog, findViewById(android.R.id.content), false) val blind: CheckBox = layout.findViewById(R.id.blind) val attended: CheckBox = layout.findViewById(R.id.attended) val transferUri: AutoCompleteTextView = layout.findViewById(R.id.transferUri) transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) transferUri.threshold = 2 transferUri.requestFocus() val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) with(builder) { setView(layout) setPositiveButton(R.string.transfer) { dialog, _ -> imm.hideSoftInputFromWindow(transferUri.windowToken, 0) dialog.dismiss() var uriText = transferUri.text.toString().trim() if (uriText.isNotEmpty()) { val uris = Contact.contactUris(uriText) if (uris.size > 1) { val destinationBuilder = MaterialAlertDialogBuilder( this@MainActivity, R.style.AlertDialogTheme ) with(destinationBuilder) { setTitle(R.string.choose_destination_uri) setItems(uris.toTypedArray()) { _, which -> uriText = uris[which] transfer( ua, if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, attended.isChecked ) } setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } show() } } else { if (uris.size == 1) uriText = uris[0] } transfer(ua, if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, attended.isChecked) } } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(transferUri.windowToken, 0) dialog.cancel() } } val alertDialog = builder.create() val call = ua.currentCall() ?: return val blindOrAttended: RelativeLayout = layout.findViewById(R.id.blindOrAttended) if (call.replaces()) { blind.setOnClickListener { if (blind.isChecked) { attended.isChecked = false alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).text = getString(R.string.transfer) } } attended.setOnClickListener { if (attended.isChecked) { blind.isChecked = false alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).text = getString(R.string.call) } } blindOrAttended.visibility = View.VISIBLE } else { blindOrAttended.visibility = View.GONE } // This needs to be done after dialog has been created and before it is shown alertDialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) alertDialog.show() } @RequiresApi(29) private fun pickupFileFromDownloads(action: String) { when (action) { "backup" -> { backupRequest.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/octet-stream" putExtra(Intent.EXTRA_TITLE, "baresip+_" + SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())) putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } "restore" -> { restoreRequest.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/octet-stream" putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } "logcat" -> { logcatRequest.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/plain" putExtra(Intent.EXTRA_TITLE, "baresip_logcat_" + SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())) putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } } } private fun transfer(ua: UserAgent, uriText: String, attended: Boolean) { val uri = if (Utils.isTelUri(uriText)) Utils.telToSip(uriText, ua.account) else Utils.uriComplete(uriText, ua.account.aor) if (!Utils.checkUri(uri)) { Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) } else { val call = ua.currentCall() if (call != null) { if (attended) { if (call.hold()) { call.referTo = uri call(ua, uri, "voice", call) } } else { if (!call.transfer(uri)) { Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.transfer_failed))) } } showCall(ua) } } } private fun askPassword(title: String, ua: UserAgent? = null) { val layout = LayoutInflater.from(this) .inflate(R.layout.password_dialog, findViewById(android.R.id.content), false) val titleView: TextView = layout.findViewById(R.id.title) titleView.text = title if (ua != null) { val messageView: TextView = layout.findViewById(R.id.message) val message = getString(R.string.account) + " " + Utils.plainAor(activityAor) messageView.text = message } val input: EditText = layout.findViewById(R.id.password) input.requestFocus() with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setView(layout) setPositiveButton(android.R.string.ok) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.dismiss() var password = input.text.toString().trim() if (!Account.checkAuthPass(password)) { Toast.makeText( applicationContext, String.format(getString(R.string.invalid_authentication_password), password), Toast.LENGTH_SHORT ).show() password = "" } when (title) { getString(R.string.encrypt_password) -> if (password != "") backup(password) getString(R.string.decrypt_password) -> if (password != "") restore(password) else -> if (password == "") { askPassword(title, ua!!) } else { Api.account_set_auth_pass(ua!!.account.accp, password) ua.account.authPass = Api.account_auth_pass(ua.account.accp) BaresipService.aorPasswords[ua.account.aor] = ua.account.authPass if (ua.account.regint == 0) Api.ua_unregister(ua.uap) else Api.ua_register(ua.uap) } } } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.cancel() } val dialog = this.create() dialog.setCancelable(false) dialog.setCanceledOnTouchOutside(false) dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.show() } } private fun askPasswords(accounts: MutableList) { if (accounts.isNotEmpty()) { val account = accounts.removeAt(0) val params = account.substringAfter(">") if ((Utils.paramValue(params, "auth_user") != "") && (Utils.paramValue(params, "auth_pass") == "")) { val aor = account.substringAfter("<").substringBefore(">") val layout = LayoutInflater.from(this) .inflate(R.layout.password_dialog, findViewById(android.R.id.content), false) val titleView: TextView = layout.findViewById(R.id.title) titleView.text = getString(R.string.authentication_password) val messageView: TextView = layout.findViewById(R.id.message) val message = getString(R.string.account) + " " + Utils.plainAor(aor) messageView.text = message val input: EditText = layout.findViewById(R.id.password) input.requestFocus() with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setView(layout) setPositiveButton(android.R.string.ok) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.dismiss() val password = input.text.toString().trim() if (!Account.checkAuthPass(password)) { Toast.makeText( applicationContext, String.format(getString(R.string.invalid_authentication_password), password), Toast.LENGTH_SHORT ).show() accounts.add(0, account) } else { BaresipService.aorPasswords[aor] = password } askPasswords(accounts) } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.cancel() BaresipService.aorPasswords[aor] = NO_AUTH_PASS askPasswords(accounts) } val dialog = this.create() dialog.setCancelable(false) dialog.setCanceledOnTouchOutside(false) dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.show() } } else { askPasswords(accounts) } } else { requestPermissionsLauncher.launch(permissions) } } private fun startBaresip() { if (!BaresipService.isStartReceived) { baresipService.action = "Start" startService(baresipService) if (atStartup) moveTaskToBack(true) } } private fun backup(password: String) { val files = arrayListOf("accounts", "config", "contacts", "call_history", "messages", "gzrtp.zid", "cert.pem", "ca_cert", "ca_certs.crt") File(BaresipService.filesPath).walk().forEach { if (it.name.endsWith(".png")) files.add(it.name) } val zipFile = getString(R.string.app_name_plus) + ".zip" val zipFilePath = BaresipService.filesPath + "/$zipFile" if (!Utils.zip(files, zipFile)) { Log.w(TAG, "Failed to write zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } val content = Utils.getFileContents(zipFilePath) if (content == null) { Log.w(TAG, "Failed to read zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } if (!Utils.encryptToUri(applicationContext, downloadsOutputUri!!, content, password)) { Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } Utils.alertView(this, getString(R.string.info), String.format(getString(R.string.backed_up), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) Utils.deleteFile(File(zipFilePath)) } private fun restore(password: String) { val zipFile = getString(R.string.app_name_plus) + ".zip" val zipFilePath = BaresipService.filesPath + "/$zipFile" val zipData = Utils.decryptFromUri(applicationContext, downloadsInputUri!!, password) if (zipData == null) { Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_failed), Utils.fileNameOfUri(applicationContext, downloadsInputUri!!))) return } if (!Utils.putFileContents(zipFilePath, zipData)) { Log.w(TAG, "Failed to write zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_failed), Utils.fileNameOfUri(applicationContext, downloadsInputUri!!))) return } if (!Utils.unZip(zipFilePath)) { Log.w(TAG, "Failed to unzip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_unzip_failed), "baresip+", "47.0.0")) return } Utils.deleteFile(File(zipFilePath)) File("${BaresipService.filesPath}/recordings").walk().forEach { if (it.name.startsWith("dump")) Utils.deleteFile(it) } CallHistoryNew.restore() for (h in BaresipService.callHistory) h.recording = arrayOf("", "") CallHistoryNew.save() with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(getString(R.string.info)) setMessage(getString(R.string.restored)) setPositiveButton(getText(R.string.restart)) { dialog, _ -> quitRestart(true) dialog.dismiss() } setNeutralButton(getText(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } private fun spinToAor(aor: String) { for (accountIndex in BaresipService.uas.indices) if (BaresipService.uas[accountIndex].account.aor == aor) { aorSpinner.setSelection(accountIndex) aorSpinner.tag = aor return } if (BaresipService.uas.isNotEmpty()) { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } else { aorSpinner.setSelection(-1) aorSpinner.tag = "" } } private fun makeCall(kind: String, lookForContact: Boolean = true) { callUri.setAdapter(null) val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor if (!Call.inCall()) { var uriText = callUri.text.toString().trim() if (uriText.isNotEmpty()) { if (lookForContact) { val uris = Contact.contactUris(uriText) if (uris.size == 1) uriText = uris[0] else if (uris.size > 1) { val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) with(builder) { setTitle(R.string.choose_destination_uri) setItems(uris.toTypedArray()) { _, which -> callUri.setText(uris[which]) makeCall(kind, false) } setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } show() } return } } if (Utils.isTelNumber(uriText)) uriText = "tel:$uriText" val uri = if (Utils.isTelUri(uriText)) { if (ua.account.telProvider == "") { Utils.alertView(this, getString(R.string.notice), String.format(getString(R.string.no_telephony_provider), aor)) return } Utils.telToSip(uriText, ua.account) } else { Utils.uriComplete(uriText, aor) } if (!Utils.checkUri(uri)) { Utils.alertView(this, getString(R.string.notice), String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) } else if (!BaresipService.requestAudioFocus(applicationContext)) { Toast.makeText(applicationContext, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show() } else { callUri.isFocusable = false uaAdapter.notifyDataSetChanged() callButton.visibility = View.INVISIBLE callButton.isEnabled = false callVideoButton.visibility = View.INVISIBLE callVideoButton.isEnabled = false switchVideoLayout(true) hangupButton.visibility = View.VISIBLE hangupButton.isEnabled = true if (Build.VERSION.SDK_INT < 31) { Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") am.mode = AudioManager.MODE_IN_COMMUNICATION runCall(ua, uri, kind) } else { if (am.mode == AudioManager.MODE_IN_COMMUNICATION) { runCall(ua, uri, kind) } else { audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> if (mode == AudioManager.MODE_IN_COMMUNICATION) { Log.d(TAG, "Audio mode changed to MODE_IN_COMMUNICATION using " + "device ${am.communicationDevice!!.type}") if (audioModeChangedListener != null) { am.removeOnModeChangedListener(audioModeChangedListener!!) audioModeChangedListener = null } runCall(ua, uri, kind) } else { Log.d(TAG, "Audio mode changed to mode ${am.mode} using " + "device ${am.communicationDevice!!.type}") } } am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!) Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") am.mode = AudioManager.MODE_IN_COMMUNICATION } } } } else { val latestPeerUri = CallHistoryNew.aorLatestPeerUri(aor) if (latestPeerUri != null) callUri.setText(Utils.friendlyUri(this, latestPeerUri, ua.account)) } } } private fun call(ua: UserAgent, uri: String, kind: String, onHoldCall: Call? = null): Boolean { if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) val videoDir = when { kind == "voice" -> Api.SDP_INACTIVE Utils.isCameraAvailable(this) -> Api.SDP_SENDRECV else -> Api.SDP_RECVONLY } val callp = ua.callAlloc(0L, Api.VIDMODE_ON) return if (callp != 0L) { Log.d(TAG, "Adding outgoing $kind call ${ua.uap}/$callp/$uri") val call = Call(callp, ua, uri, "out", "outgoing", Utils.dtmfWatcher(callp)) call.onHoldCall = onHoldCall call.setMediaDirection(Api.SDP_SENDRECV, videoDir) call.add() if (onHoldCall != null) onHoldCall.newCall = call if (call.connect(uri)) { showCall(ua) true } else { Log.w(TAG, "call_connect $callp failed") if (onHoldCall != null) onHoldCall.newCall = null call.remove() call.destroy() showCall(ua) false } } else { Log.w(TAG, "callAlloc for ${ua.uap} to $uri failed") false } } private fun acceptTransfer(ua: UserAgent, call: Call, uri: String) { val newCallp = ua.callAlloc(call.callp, Api.VIDMODE_OFF) if (newCallp != 0L) { Log.d(TAG, "Adding outgoing call ${ua.uap}/$newCallp/$uri") val newCall = Call(newCallp, ua, uri, "out", "transferring", Utils.dtmfWatcher(newCallp)) newCall.add() if (newCall.connect(uri)) { if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) showCall(ua) } else { Log.w(TAG, "call_connect $newCallp failed") call.notifySipfrag(500, "Call Error") } } else { Log.w(TAG, "callAlloc for ua ${ua.uap} call ${call.callp} transfer failed") call.notifySipfrag(500, "Call Error") } } private fun setVideoSecurityButton(security: Int) { when (security) { R.drawable.unlocked -> { videoSecurityButton.setImageResource(R.drawable.unlocked_video) } R.drawable.locked_yellow -> { videoSecurityButton.setImageResource(R.drawable.locked_video_yellow) } R.drawable.locked_green -> { videoSecurityButton.setImageResource(R.drawable.locked_video_green) } } } private fun runCall(ua: UserAgent, uri: String, kind: String) { callRunnable = Runnable { callRunnable = null if (!call(ua, uri, kind)) { BaresipService.abandonAudioFocus(applicationContext) if(BaresipService.isSupportAudioCall) { callButton.visibility = View.VISIBLE callButton.isEnabled = true } callVideoButton.visibility = View.VISIBLE callVideoButton.isEnabled = true hangupButton.visibility = View.INVISIBLE hangupButton.isEnabled = false } } callHandler.postDelayed(callRunnable!!, BaresipService.audioDelay) } private fun showCall(ua: UserAgent, showCall: Call? = null) { val call = showCall ?: ua.currentCall() if (call == null) { swipeRefresh.isEnabled = true videoLayout.visibility = View.INVISIBLE defaultLayout.visibility = View.VISIBLE callTitle.text = getString(R.string.outgoing_call_to_dots) callTimer.visibility = View.INVISIBLE if (ua.account.resumeUri != "") callUri.setText(ua.account.resumeUri) else callUri.text.clear() callUri.hint = getString(R.string.callee) callUri.isFocusable = true callUri.isFocusableInTouchMode = true imm.hideSoftInputFromWindow(callUri.windowToken, 0) callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) securityButton.visibility = View.INVISIBLE diverter.visibility = View.GONE if(BaresipService.isSupportAudioCall) { callButton.visibility = View.VISIBLE callButton.isEnabled = true } callVideoButton.visibility = View.VISIBLE callVideoButton.isEnabled = true hangupButton.visibility = View.INVISIBLE answerButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE callControl.visibility = View.INVISIBLE dialpadButton.isEnabled = true videoButton.visibility = View.INVISIBLE if (BaresipService.isMicMuted) { BaresipService.isMicMuted = false if(micIcon != null) { micIcon!!.setIcon(R.drawable.mic_on) } } onHoldNotice.visibility = View.GONE } else { swipeRefresh.isEnabled = false callUri.isFocusable = false when (call.status) { "outgoing", "transferring", "answered" -> { callTitle.text = if (call.status == "answered") getString(R.string.incoming_call_from_dots) else getString(R.string.outgoing_call_to_dots) callTimer.visibility = View.INVISIBLE callUri.setText(Utils.friendlyUri(this, call.peerUri, ua.account, call.status == "answered")) videoButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE diverter.visibility = View.GONE callButton.visibility = View.INVISIBLE hangupButton.visibility = View.VISIBLE hangupButton.isEnabled = true answerButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE callControl.visibility = View.INVISIBLE onHoldNotice.visibility = View.GONE dialpadButton.isEnabled = false } "incoming" -> { callTitle.text = getString(R.string.incoming_call_from_dots) callTimer.visibility = View.INVISIBLE val caller = Utils.friendlyUri(this, call.peerUri, ua.account) callUri.setText(caller) callUri.setAdapter(null) videoButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE val uri = call.diverterUri() if (uri != "") { diverterUri.text = Utils.friendlyUri(this, uri, ua.account) diverter.visibility = View.VISIBLE } else { diverter.visibility = View.GONE } callButton.visibility = View.INVISIBLE callVideoButton.visibility = View.INVISIBLE switchVideoLayout(true) hangupButton.visibility = View.INVISIBLE answerButton.visibility = View.VISIBLE answerButton.isEnabled = true if (call.hasVideo()) { answerVideoButton.visibility = View.VISIBLE answerVideoButton.isEnabled = true } else { answerVideoButton.visibility = View.INVISIBLE answerVideoButton.isEnabled = false } rejectButton.visibility = View.VISIBLE rejectButton.isEnabled = true callControl.visibility = View.INVISIBLE onHoldNotice.visibility = View.GONE dialpadButton.isEnabled = false } "connected" -> { if (call.videoEnabled()) { if (defaultLayout.visibility == View.VISIBLE) { defaultLayout.visibility = View.INVISIBLE videoLayout.visibility = View.VISIBLE } videoOnHoldNotice.visibility = if (call.held) View.VISIBLE else View.GONE if (ua.account.mediaEnc == "") { videoSecurityButton.visibility = View.INVISIBLE } else { securityButton.tag = call.security setVideoSecurityButton(call.security) videoSecurityButton.visibility = View.INVISIBLE // 암호화 상태 버튼 표시 안함 by ritoseo //videoSecurityButton.visibility = View.VISIBLE } return } if (defaultLayout.visibility == View.INVISIBLE) { videoLayout.visibility = View.INVISIBLE defaultLayout.visibility = View.VISIBLE } callControl.post { callControl.scrollTo(videoButton.left, videoButton.top) } if (call.referTo != "") { callTitle.text = getString(R.string.transferring_call_to_dots) callUri.setText(Utils.friendlyUri(this, call.referTo, ua.account)) transferButton.isEnabled = false } else { if (call.dir == "out") { callTitle.text = getString(R.string.outgoing_call_to_dots) callUri.setText(Utils.friendlyUri(this, call.peerUri, ua.account)) } else { callTitle.text = getString(R.string.incoming_call_from_dots) callUri.setText(Utils.friendlyUri(this, call.peerUri, ua.account)) } transferButton.isEnabled = true } if (call.onHoldCall == null) transferButton.setImageResource(R.drawable.call_transfer) else transferButton.setImageResource(R.drawable.call_transfer_execute) startCallTimer(call) callTimer.visibility = View.VISIBLE videoButton.setImageResource(R.drawable.video_on) videoButton.visibility = View.VISIBLE videoButton.isClickable = true if (ua.account.mediaEnc == "") { securityButton.visibility = View.INVISIBLE } else { securityButton.tag = call.security securityButton.setImageResource(call.security) securityButton.visibility = View.VISIBLE } callButton.visibility = View.INVISIBLE callVideoButton.visibility = View.INVISIBLE switchVideoLayout(true) hangupButton.visibility = View.VISIBLE hangupButton.isEnabled = true answerButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE /* if (call.onhold) { holdButton.setImageResource(R.drawable.resume) } else { holdButton.setImageResource(R.drawable.call_hold) } dialpadButton.setImageResource(R.drawable.dialpad_on) dialpadButton.tag = "on" dialpadButton.isEnabled = false infoButton.isEnabled = true callControl.visibility = View.VISIBLE Handler(Looper.getMainLooper()).postDelayed({ onHoldNotice.visibility = if (call.held) View.VISIBLE else View.GONE }, 100) if (call.held) { imm.hideSoftInputFromWindow(dtmf.windowToken, 0) dtmf.isEnabled = false } else { dtmf.isEnabled = true dtmf.requestFocus() if (resources.configuration.orientation == ORIENTATION_PORTRAIT) imm.showSoftInput(dtmf, InputMethodManager.SHOW_IMPLICIT) if (dtmfWatcher != null) dtmf.removeTextChangedListener(dtmfWatcher) dtmfWatcher = call.dtmfWatcher dtmf.addTextChangedListener(dtmfWatcher) } */ } } } } private fun updateIcons(acc: Account) { if (acc.missedCalls) callsButton.setImageResource(R.drawable.calls_missed) else callsButton.setImageResource(R.drawable.calls) if (acc.unreadMessages) messagesButton.setImageResource(R.drawable.messages_unread) else messagesButton.setImageResource(R.drawable.messages) if (acc.vmUri != "") { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) else voicemailButton.setImageResource(R.drawable.voicemail) voicemailButton.visibility = View.VISIBLE voicemailButtonSpace.visibility = View.VISIBLE } else { voicemailButton.visibility = View.GONE voicemailButtonSpace.visibility =View.GONE } } private fun restoreActivities() { if (BaresipService.activities.isEmpty()) return Log.d(TAG, "Activity stack ${BaresipService.activities}") val activity = BaresipService.activities[0].split(",") BaresipService.activities.removeAt(0) when (activity[0]) { "main" -> { if (!Call.inCall() && (BaresipService.activities.size > 1)) restoreActivities() } "config" -> { configRequest.launch(Intent(this, ConfigActivity::class.java)) } "audio" -> { startActivity(Intent(this, AudioActivity::class.java)) } "accounts" -> { val i = Intent(this, AccountsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) accountsRequest.launch(i) } "account" -> { val i = Intent(this, AccountActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) accountsRequest.launch(i) } "codecs" -> { val i = Intent(this, CodecsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("media", activity[2]) i.putExtras(b) startActivity(i) } "about" -> { startActivity(Intent(this, AboutActivity::class.java)) } "contacts" -> { val i = Intent(this, ContactsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) contactsRequest.launch(i) } "contact" -> { val i = Intent(this, ContactActivity::class.java) val b = Bundle() if (activity[1] == "true") { b.putBoolean("new", true) b.putString("uri", activity[2]) } else { b.putBoolean("new", false) b.putInt("index", activity[2].toInt()) } i.putExtras(b) startActivity(i) } "chats" -> { val i = Intent(this, ChatsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) chatRequests.launch(i) } "chat" -> { val i = Intent(this, ChatActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("peer", activity[2]) b.putBoolean("focus", activity[3] == "true") i.putExtras(b) chatRequests.launch(i) } "calls" -> { val i = Intent(this, CallsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) callsRequest.launch(i) } "call_details" -> { val i = Intent(this, CallDetailsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("peer", activity[2]) b.putInt("position", activity[3].toInt()) i.putExtras(b) callsRequest.launch(i) } } return } private fun saveCallUri() { if (BaresipService.uas.isNotEmpty() && aorSpinner.selectedItemPosition >= 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (ua.calls().isEmpty()) ua.account.resumeUri = callUri.text.toString() else ua.account.resumeUri = "" } } private fun startCallTimer(call: Call) { callTimer.stop() callTimer.base = SystemClock.elapsedRealtime() - (call.duration() * 1000L) callTimer.start() } companion object { var accountRequest: ActivityResultLauncher? = null var activityAor = "" lateinit var videoView: VideoView } init { if (!BaresipService.libraryLoaded) { Log.i(TAG, "Loading baresip library") System.loadLibrary("baresip") BaresipService.libraryLoaded = true } } }