4549 lines
188 KiB
Kotlin
Raw Normal View History

2025-03-09 23:45:43 +09:00
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.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.Rect
2025-03-09 23:45:43 +09:00
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.OptIn
2025-03-09 23:45:43 +09:00
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.AppCompatButton
import androidx.camera.view.PreviewView
2025-03-09 23:45:43 +09:00
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.core.view.isVisible
2025-03-09 23:45:43 +09:00
import androidx.lifecycle.Observer
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
2025-08-26 23:36:34 +09:00
import com.tutpro.baresip.plus.Utils.getAppVersion
2025-03-09 23:45:43 +09:00
import com.tutpro.baresip.plus.Utils.showSnackBar
import com.tutpro.baresip.plus.databinding.ActivityMainBinding
2025-08-26 23:36:34 +09:00
import org.json.JSONObject
2025-03-09 23:45:43 +09:00
import java.io.File
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.Executors
2025-03-09 23:45:43 +09:00
import kotlin.system.exitProcess
import java.io.FileOutputStream
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
2025-03-09 23:45:43 +09:00
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)
}
}
private class FrameSaver(
private val outputDir: File
) : ImageAnalysis.Analyzer {
private var lastSavedAt = 0L
private val sdf = SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US)
override fun analyze(image: ImageProxy) {
try {
val now = System.currentTimeMillis()
// 1초 간격
if (now - lastSavedAt >= 1000) {
val bitmap = imageProxyToBitmap(image) ?: return
// 회전 보정
val rotated = rotateBitmap(bitmap, image.imageInfo.rotationDegrees.toFloat())
// 파일 저장 (JPG)
var nearFilePath = "/mnt/obb/near_${BaresipService.captureCount}.jpg"
//val file = File(outputDir, "${sdf.format(now)}.jpg")
val file = File(nearFilePath)
FileOutputStream(file).use { fos ->
rotated.compress(Bitmap.CompressFormat.JPEG, 90, fos)
val destFile = File(nearFilePath)
destFile.setReadable(true, false)
Utils.propertySet("sys.ritosip.near_screen_file", nearFilePath)
}
lastSavedAt = now
}
} catch (t: Throwable) {
t.printStackTrace()
} finally {
image.close() // 반드시 닫아야 다음 프레임이 들어옵니다.
}
}
// RGBA_8888 → Bitmap
private fun imageProxyToBitmap(image: ImageProxy): Bitmap? {
// if (image.format != ImageFormat.UNKNOWN &&
// image.format != ImageFormat.RGBA_8888
// ) return null
val plane = image.planes.firstOrNull() ?: return null
val buffer = plane.buffer
buffer.rewind()
val width = image.width
val height = image.height
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(buffer)
return bitmap
}
private fun rotateBitmap(src: Bitmap, degrees: Float): Bitmap {
if (degrees == 0f) return src
val m = Matrix().apply { postRotate(degrees) }
return Bitmap.createBitmap(src, 0, 0, src.width, src.height, m, true).also {
if (it != src) src.recycle()
}
}
}
2025-03-09 23:45:43 +09:00
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<Event<Long>>
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<String>
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private lateinit var accountsRequest: ActivityResultLauncher<Intent>
private lateinit var chatRequests: ActivityResultLauncher<Intent>
private lateinit var configRequest: ActivityResultLauncher<Intent>
private lateinit var backupRequest: ActivityResultLauncher<Intent>
private lateinit var restoreRequest: ActivityResultLauncher<Intent>
private lateinit var logcatRequest: ActivityResultLauncher<Intent>
private lateinit var contactsRequest: ActivityResultLauncher<Intent>
private lateinit var callsRequest: ActivityResultLauncher<Intent>
private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener
private lateinit var permissions: Array<String>
private lateinit var dialogImgBtn1: AppCompatButton
private lateinit var dialogImgBtn2: AppCompatButton
private lateinit var callStartButton: AppCompatButton
private lateinit var callHistoryButton: AppCompatButton
private lateinit var settingButton: AppCompatButton
2025-08-26 23:36:34 +09:00
private lateinit var layoutButton: AppCompatButton
private lateinit var serverButton: AppCompatButton
private lateinit var volumeButton: AppCompatButton
private lateinit var networkButton: AppCompatButton
private lateinit var backwardButton: AppCompatButton
private lateinit var previewView: PreviewView
private var imageAnalyzer: ImageAnalysis? = null
private var cameraExecutor = Executors.newSingleThreadExecutor()
2025-03-09 23:45:43 +09:00
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)
}
}
private fun setDpadNavigation(
view: View,
upView: View? = null,
downView: View? = null
) {
view.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
upView?.requestFocus()
true
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
downView?.requestFocus()
true
}
else -> false
}
} else {
false
}
}
}
lateinit var navUpList : HashMap<View, View>
lateinit var navUpListAlter : HashMap<View, View>
lateinit var navDownList : HashMap<View, View>
lateinit var navDownListAlter : HashMap<View, View>
2025-08-26 23:36:34 +09:00
lateinit var navUpServerList : HashMap<View, View>
lateinit var navDownServerList : HashMap<View, View>
private var cameraProvider: ProcessCameraProvider? = null
@OptIn(ExperimentalCamera2Interop::class)
private fun cameraSelectorById(targetId: String): CameraSelector {
return CameraSelector.Builder()
.addCameraFilter { cameraInfos: List<CameraInfo> ->
cameraInfos.filter { info ->
Camera2CameraInfo.from(info).cameraId == targetId
}
}
.build()
}
private fun stopSelfMoitoringCamera() {
if(BaresipService.useMonitoringSelfView) {
binding.selfViewLayout.visibility = View.INVISIBLE
// val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
// val cameraProvider = cameraProviderFuture.get()
cameraProvider?.unbindAll()
}
}
@SuppressLint("RestrictedApi")
private fun resetSelfMoitoringCamera() {
if(BaresipService.useMonitoringSelfView) {
for(i in 0..1) {
cameraProvider?.unbindAll()
cameraProvider?.shutdown()
cameraExecutor.shutdown()
}
cameraExecutor = Executors.newSingleThreadExecutor()
}
}
// @SuppressLint("RestrictedApi")
// private fun reinitializeCamera(reason: String) {
// // 1) 모든 UseCase 해제
// cameraProvider?.unbindAll()
//
// // 2) provider 완전 종료
// val oldProvider = cameraProvider
// cameraProvider = null
//
// if (oldProvider != null) {
// val shutdownFuture = oldProvider.shutdown() // ListenableFuture<Void>
// shutdownFuture.addListener({
// // 3) 재시작
// startCameraWithId(targetCameraId)
// }, ContextCompat.getMainExecutor(this))
// } else {
// // 바로 시작
// startCameraWithId(targetCameraId)
// }
// }
private fun startSelfMoitoringCamera() {
if(!BaresipService.useMonitoringSelfView)
return;
binding.selfViewLayout.visibility = View.VISIBLE
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
// ImageAnalysis: 프리뷰 프레임을 가져옴
imageAnalyzer = ImageAnalysis.Builder()
// RGBA_8888로 받으면 YUV→RGB 변환 없이 바로 Bitmap 생성 가능
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
// 최신 프레임만 유지 (버벅임 방지)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { ia ->
ia.setAnalyzer(cameraExecutor, FrameSaver(getOutputDir()))
}
val cameraSelector = cameraSelectorById("120")
try {
cameraProvider?.unbindAll()
val camera = cameraProvider?.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
camera?.cameraInfo?.cameraState?.observe(this) { state ->
when (val type = state.type) {
CameraState.Type.OPEN, CameraState.Type.PENDING_OPEN -> { /* 정상/열리는 중 */ }
CameraState.Type.CLOSING, CameraState.Type.CLOSED -> {
// 닫힘 → 필요 시 재시도 타이머
println("RITO Camera Closing or closed")
}
else -> {}
}
state.error?.let { err ->
when (err.code) {
CameraState.ERROR_CAMERA_IN_USE,
CameraState.ERROR_MAX_CAMERAS_IN_USE,
CameraState.ERROR_CAMERA_DISABLED,
CameraState.ERROR_CAMERA_FATAL_ERROR -> {
// 복구 트리거
println("RITO Camera Error code : ${err.code}")
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}, ContextCompat.getMainExecutor(this))
}
private fun getOutputDir(): File {
// 앱 전용 Pictures 폴더 (권한 불필요)
val dir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File(dir, "preview_frames").apply { if (!exists()) mkdirs() }
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if(!isKeyboardVisible) {
val currentFocusView = currentFocus
if(event.action == KeyEvent.ACTION_UP) {
println("pokaRITO pokachip z. " + event + " focusView : " + currentFocusView)
if(event.keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if(navUpList.contains(currentFocusView)) {
val view = navUpList.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
} else {
if(navUpListAlter.contains(currentFocusView)) {
val view = navUpListAlter.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
}
}
// if(currentFocusView == binding.btnApply) {
// binding.radioDhcp.requestFocus()
// return false
// }
}
} else {
if(currentFocusView == binding.aorSpinner) {
callStartButton.requestFocus()
return false
}
}
2025-08-26 23:36:34 +09:00
if(navUpServerList.contains(currentFocusView)) {
val view = navUpServerList.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
} else {
if(currentFocusView == binding.btnApply) {
binding.radioDhcp.requestFocus()
return false
}
}
} else {
if(currentFocusView == binding.aorSpinner) {
callStartButton.requestFocus()
return false
}
}
}
else if(event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if(navDownList.contains(currentFocusView)) {
val view = navDownList.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
} else {
if(navDownListAlter.contains(currentFocusView)) {
val view = navDownListAlter.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
}
}
}
}
2025-08-26 23:36:34 +09:00
if(navDownServerList.contains(currentFocusView)) {
val view = navDownServerList.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
}
}
} else if(event.keyCode == KeyEvent.KEYCODE_ENTER) {
if(currentFocusView == binding.editIp ||
currentFocusView == binding.editGateway ||
currentFocusView == binding.editNetmask ||
2025-08-26 23:36:34 +09:00
currentFocusView == binding.editDns ||
currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword
) {
if(imm.isActive) {
imm.showSoftInput(currentFocusView, InputMethodManager.SHOW_IMPLICIT)
imm.restartInput(currentFocusView)
return true
}
}
}
} else if(event.action == KeyEvent.ACTION_DOWN) {
if(event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN || event.keyCode == KeyEvent.KEYCODE_DPAD_UP) {
var found = false
if(navUpList.contains(currentFocusView) || navDownList.contains(currentFocusView)) {
found = true
}
// if(!found && navDownListAlter.contains(currentFocusView)) {
// found = true
// }
2025-08-26 23:36:34 +09:00
if(navUpServerList.contains(currentFocusView) || navDownServerList.contains(currentFocusView)) {
found = true
}
if(found) {
return true
}
} else if(event.keyCode == KeyEvent.KEYCODE_ENTER) {
if(currentFocusView == binding.editIp ||
currentFocusView == binding.editGateway ||
currentFocusView == binding.editNetmask ||
currentFocusView == binding.editDns) {
return true
}
2025-08-26 23:36:34 +09:00
if(currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword) {
return true
}
}
}
} else {
// if(event.keyCode == KeyEvent.KEYCODE_BACK) {
// val currentFocusView = currentFocus
// if(currentFocusView != null) {
// imm.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
// }
// }
}
// if (event.action == KeyEvent.ACTION_UP && event.keyCode == KeyEvent.KEYCODE_BACK) {
// val currentFocusView = currentFocus
// println("pokaRITO pokachip 0")
// if (currentFocusView is EditText) {
// println("pokaRITO pokachip 1")
// // 키보드가 내려간 후 300ms 뒤에 포커스를 재설정
// Handler(Looper.getMainLooper()).postDelayed({
// currentFocusView.requestFocus()
// }, 300)
// }
// }
return super.dispatchKeyEvent(event)
}
private var isKeyboardVisible = false
private fun setupKeyboardVisibilityListener() {
val rootView = findViewById<View>(android.R.id.content)
rootView.viewTreeObserver.addOnGlobalLayoutListener {
val rect = Rect()
rootView.getWindowVisibleDisplayFrame(rect)
val screenHeight = rootView.rootView.height
val keypadHeight = screenHeight - rect.bottom
val visible = keypadHeight > screenHeight * 0.15 // 키보드 올라옴 판단 기준
if (isKeyboardVisible && !visible) {
// 🔻 키보드가 막 내려간 순간!
onKeyboardHidden()
}
isKeyboardVisible = visible
}
}
private fun onKeyboardHidden() {
println( "키보드가 내려갔습니다!")
// val currentFocusView = currentFocus
// if(currentFocusView != null) {
// println( "hideSoftInputFromWindow : " + currentFocusView)
// imm.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
// imm.restartInput(currentFocusView)
// currentFocusView.clearFocus()
// currentFocusView.invalidate()
// currentFocusView.requestFocus()
// }
// Handler(Looper.getMainLooper()).postDelayed({
// findViewById<EditText>(R.id.editIp).requestFocus()
// findViewById<EditText>(R.id.editDns).requestFocus()
// }, 100) // 지연은 상황 따라 조정
}
private fun updateFieldsVisibility() {
val isStatic = binding.radioStatic.isChecked
val visibility = if (isStatic) View.VISIBLE else View.GONE
binding.layoutStaticIp.visibility = visibility
binding.editIp.visibility = visibility
binding.editNetmask.visibility = visibility
binding.editGateway.visibility = visibility
binding.editDns.visibility = visibility
}
fun showCustomToast(context: Context, message: String) {
val inflater = LayoutInflater.from(context)
val layout: View = inflater.inflate(R.layout.custom_toast, null)
val textView: TextView = layout.findViewById(R.id.toast_text)
textView.text = message
val toast = Toast(context)
toast.duration = Toast.LENGTH_LONG
toast.view = layout
toast.show()
}
2025-03-09 23:45:43 +09:00
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
// theme.applyStyle(R.style.OptOutEdgeToEdgeEnforcement,false)
super.onCreate(savedInstanceState)
// if (BuildConfig.DEBUG) {
//
// }
//
// StrictMode.setVmPolicy(
// VmPolicy.Builder()
// .detectLeakedClosableObjects() // ✨ close() 호출 누락 감지
// .penaltyLog() // Logcat에 로그 출력
// .penaltyDeath() // 앱 크래시 (원인 추적 용이)
// .build()
// )
2025-03-09 23:45:43 +09:00
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
}
setupKeyboardVisibilityListener()
2025-08-26 23:36:34 +09:00
Utils.propertySet("sys.ritosip.version", getAppVersion(this))
2025-03-09 23:45:43 +09:00
// 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
previewView = binding.previewView
dialogImgBtn1 = binding.dialogButtonImg1
dialogImgBtn1.setOnClickListener {
}
dialogImgBtn2 = binding.dialogButtonImg2
callStartButton = binding.callStartBtn
callStartButton.setOnClickListener {
val status = Utils.propertyGet("sys.ritosip.sip.status")
if(status == "registered") {
callVideoButton.performClick()
} else {
showCustomToast(this, "SIP서버에 연결 되어 있지 않습니다.")
}
}
//callHistoryButton = binding.callHistoryBtn
settingButton = binding.settingBtn
settingButton.setOnClickListener {
// val i = Intent(this, NetworkSettingActivity::class.java)
// i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
// Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
// startActivity(i)
// binding.baseButtonLayout.visibility = View.INVISIBLE
// binding.mainActivityLayout.visibility = View.INVISIBLE
// binding.defaultLayout.visibility = View.INVISIBLE
2025-08-26 23:36:34 +09:00
// binding.networkSettingLayout.visibility = View.VISIBLE
// binding.radioDhcp.requestFocus()
binding.settingLayout.visibility = View.VISIBLE
binding.dialogButtonLayout.requestFocus()
}
networkButton = binding.dialogButtonNetwork
networkButton.setOnClickListener {
binding.networkSettingLayout.bringToFront()
binding.networkSettingLayout.visibility = View.VISIBLE
binding.radioDhcp.requestFocus()
}
2025-08-26 23:36:34 +09:00
layoutButton = binding.dialogButtonLayout
layoutButton.setOnClickListener {
binding.layoutSettingLayout.bringToFront()
binding.layoutSettingLayout.visibility = View.VISIBLE
var splitMode = Utils.propertyGet("sys.ritosip.display_split_mode")
if(splitMode == "2") {
binding.layoutType2.requestFocus()
} else if(splitMode == "3") {
binding.layoutType3.requestFocus()
} else if(splitMode == "4") {
binding.layoutType4.requestFocus()
} else if(splitMode == "5") {
binding.layoutType5.requestFocus()
} else if(splitMode == "6") {
binding.layoutType6.requestFocus()
} else if(splitMode == "7") {
binding.layoutType7.requestFocus()
} else if(splitMode == "8") {
binding.layoutType8.requestFocus()
} else if(splitMode == "9") {
binding.layoutType9.requestFocus()
} else if(splitMode == "10") {
binding.layoutType10.requestFocus()
} else {
binding.layoutType1.requestFocus()
}
}
binding.layoutType1.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout1\"}")
}
binding.layoutType2.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout2\"}")
}
binding.layoutType3.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout3\"}")
}
binding.layoutType4.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout4\"}")
}
binding.layoutType5.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout5\"}")
}
binding.layoutType6.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout6\"}")
}
binding.layoutType7.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout7\"}")
}
binding.layoutType8.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout8\"}")
}
binding.layoutType9.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout9\"}")
}
binding.layoutType10.setOnClickListener {
sendSettingByBroadcast("layout_setting", "{\"layout\":\"layout10\"}")
}
binding.dialogButtonLayoutInCall.setOnClickListener {
binding.layoutSettingLayout.bringToFront()
binding.layoutSettingLayout.visibility = View.VISIBLE
var splitMode = Utils.propertyGet("sys.ritosip.display_split_mode")
if(splitMode == "2") {
binding.layoutType2.requestFocus()
} else if(splitMode == "3") {
binding.layoutType3.requestFocus()
} else if(splitMode == "4") {
binding.layoutType4.requestFocus()
} else if(splitMode == "5") {
binding.layoutType5.requestFocus()
} else if(splitMode == "6") {
binding.layoutType6.requestFocus()
} else if(splitMode == "7") {
binding.layoutType7.requestFocus()
} else if(splitMode == "8") {
binding.layoutType8.requestFocus()
} else if(splitMode == "9") {
binding.layoutType9.requestFocus()
} else if(splitMode == "10") {
binding.layoutType10.requestFocus()
} else {
binding.layoutType1.requestFocus()
}
}
serverButton = binding.dialogButtonServer
serverButton.setOnClickListener {
binding.serverSettingLayout.bringToFront()
binding.serverSettingLayout.visibility = View.VISIBLE
binding.editDisplayName.setText(Utils.propertyGet("sys.ritosip.account.display_name"))
binding.editSipId.setText(Utils.propertyGet("sys.ritosip.account.account_name"))
binding.editSipServer.setText(Utils.propertyGet("sys.ritosip.account.server_address"))
var transport = Utils.propertyGet("sys.ritosip.account.prefer_transport")
if(transport == "tcp") {
binding.radioTransportTcp.isChecked = true
} else if(transport == "tls") {
binding.radioTransportTls.isChecked = true
} else {
binding.radioTransportUdp.isChecked = true
}
var encryption = Utils.propertyGet("sys.ritosip.account.media_encryption")
if(encryption == "srtp") {
binding.radioEncryptSrtp.isChecked = true
} else {
binding.radioEncryptNone.isChecked = true
}
binding.editDisplayName.requestFocus()
}
volumeButton = binding.dialogButtonVolume
volumeButton.setOnClickListener {
binding.volumeSettingLayout.bringToFront()
binding.volumeSettingLayout.visibility = View.VISIBLE
binding.seekbarMicVolume.requestFocus()
binding.seekbarMicVolume.progress = BaresipService.micVolume
val currentValue = binding.seekbarMicVolume.progress
binding.textMicVolume.setText(currentValue.toString())
binding.seekbarAuxVolume.progress = BaresipService.auxVolume
val currentValueAux = binding.seekbarAuxVolume.progress
binding.textAuxVolume.setText(currentValueAux.toString())
2025-08-26 23:36:34 +09:00
}
binding.dialogButtonVolumeInCall.setOnClickListener {
binding.volumeSettingLayout.bringToFront()
binding.volumeSettingLayout.visibility = View.VISIBLE
binding.seekbarMicVolume.requestFocus()
binding.seekbarMicVolume.progress = BaresipService.micVolume
val currentValue = binding.seekbarMicVolume.progress
binding.textMicVolume.setText(currentValue.toString())
binding.seekbarAuxVolume.progress = BaresipService.auxVolume
val currentValueAux = binding.seekbarAuxVolume.progress
binding.textAuxVolume.setText(currentValueAux.toString())
2025-08-26 23:36:34 +09:00
}
binding.seekbarMicVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
// progress: 현재 값
// fromUser: 사용자가 직접 움직였는지 여부
val currentValue = progress
println("현재 MIC 값: $currentValue")
2025-08-26 23:36:34 +09:00
binding.textMicVolume.setText(currentValue.toString())
Utils.setMicVolumeByMix(currentValue)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
// 터치 시작 시 호출
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
// 터치 끝날 때 호출
}
})
binding.seekbarAuxVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
// progress: 현재 값
// fromUser: 사용자가 직접 움직였는지 여부
val currentValue = progress
println("현재 AUX 값: $currentValue")
binding.textAuxVolume.setText(currentValue.toString())
Utils.setAuxVolumeByMix(currentValue)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
// 터치 시작 시 호출
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
// 터치 끝날 때 호출
}
})
2025-08-26 23:36:34 +09:00
binding.btnDoneVolume.setOnClickListener {
binding.volumeSettingLayout.visibility = View.INVISIBLE
if(binding.settingLayout.visibility == View.VISIBLE) {
binding.dialogButtonVolume.requestFocus()
} else if(binding.settingLayoutInCall.visibility == View.VISIBLE) {
binding.dialogButtonVolumeInCall.requestFocus()
}
}
backwardButton = binding.dialogButtonBackward
backwardButton.setOnClickListener {
if(binding.settingLayout.visibility == View.VISIBLE) {
binding.settingLayout.visibility = View.INVISIBLE
binding.settingBtn.requestFocus()
}
}
binding.dialogButtonHangUp.setOnClickListener {
if(binding.settingLayoutInCall.visibility == View.VISIBLE) {
hangupButton.performClick()
binding.settingLayoutInCall.visibility = View.INVISIBLE
}
}
binding.dialogButtonBackwardInCall.setOnClickListener {
if(binding.settingLayoutInCall.visibility == View.VISIBLE) {
binding.settingLayoutInCall.visibility = View.INVISIBLE
}
}
binding.layoutBackward.setOnClickListener {
binding.layoutSettingLayout.visibility = View.INVISIBLE
if(binding.settingLayout.visibility == View.VISIBLE) {
binding.dialogButtonLayout.requestFocus()
} else if(binding.settingLayoutInCall.visibility == View.VISIBLE) {
binding.dialogButtonLayoutInCall.requestFocus()
}
}
2025-03-09 23:45:43 +09:00
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
binding.editIp.setOnClickListener {
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
}
binding.editNetmask.setOnClickListener {
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
}
binding.editGateway.setOnClickListener {
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
}
binding.editDns.setOnClickListener {
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
}
navUpList = HashMap<View, View>()
navUpListAlter = HashMap<View, View>()
navDownList = HashMap<View, View>()
navDownListAlter = HashMap<View, View>()
navUpList.put(binding.editIp, binding.radioStatic)
navUpList.put(binding.editNetmask, binding.editIp)
navUpList.put(binding.editGateway, binding.editNetmask)
navUpList.put(binding.editDns, binding.editGateway)
navUpList.put(binding.btnApply, binding.editDns)
navUpList.put(binding.btnCancel, binding.editDns)
navUpListAlter.put(binding.btnCancel, binding.radioStatic)
navUpListAlter.put(binding.btnApply, binding.radioDhcp)
//navUpList.put(binding.btnApply, binding.radioDhcp)
navDownList.put(binding.radioDhcp, binding.editIp)
navDownList.put(binding.radioStatic, binding.editIp)
navDownListAlter.put(binding.radioStatic, binding.btnApply)
navDownListAlter.put(binding.radioDhcp, binding.btnApply)
navDownList.put(binding.editIp, binding.editNetmask)
navDownList.put(binding.editNetmask, binding.editGateway)
navDownList.put(binding.editGateway, binding.editDns)
navDownList.put(binding.editDns, binding.btnApply)
//navDownList.put(binding.radioDhcp, binding.btnApply)
// setDpadNavigation(binding.editIp, downView = binding.editNetmask)
// setDpadNavigation(binding.editNetmask, upView = binding.editIp, downView = binding.editGateway)
// setDpadNavigation(binding.editGateway, upView = binding.editNetmask, downView = binding.editDns)
// setDpadNavigation(binding.editDns, upView = binding.editGateway, downView = binding.btnApply)
// setDpadNavigation(binding.radioStatic, downView = binding.editIp)
binding.btnApply.setOnClickListener { _ ->
if(binding.radioDhcp.isChecked) {
val params: HashMap<String, String> = HashMap<String, String>()
params.put("IPV4TYPE", "DHCP")
params.put("DEVTYPE", "ETHERNET")
Utils.sendRequestToFactory(applicationContext, "Ipv4", params)
// Toast.makeText(
// applicationContext,
// "DHCP 적용되었습니다.",
// Toast.LENGTH_SHORT
// ).show()
showCustomToast(applicationContext, "DHCP 적용되었습니다.")
binding.networkSettingLayout.visibility = View.INVISIBLE
} else {
if(binding.editIp.text.length == 0 ||
binding.editNetmask.text.length == 0 ||
binding.editGateway.text.length == 0 ||
binding.editDns.text.length == 0) {
showCustomToast(applicationContext, "비어있는 항목이 있습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editIp.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "IP가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editNetmask.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "NETMASK가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editGateway.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "GATEWAY가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editDns.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "DNS가 유효하지 않습니다.")
return@setOnClickListener
}
val params: HashMap<String, String> = HashMap<String, String>()
params.put("IPV4TYPE", "STATIC")
params.put("DEVTYPE", "ETHERNET")
params.put("ADDRESS", binding.editIp.text.toString())
params.put("NETMASK", binding.editNetmask.text.toString())
params.put("GATEWAY", binding.editGateway.text.toString())
params.put("DNS", binding.editDns.text.toString())
Utils.sendRequestToFactory(applicationContext, "Ipv4", params)
// Toast.makeText(
// applicationContext,
// "STATIC 적용되었습니다.",
// Toast.LENGTH_SHORT
// ).show()
showCustomToast(applicationContext, "STATIC 적용되었습니다.")
binding.networkSettingLayout.visibility = View.INVISIBLE
}
2025-08-26 23:36:34 +09:00
binding.dialogButtonNetwork.requestFocus()
}
2025-08-26 23:36:34 +09:00
binding.btnCancel.setOnClickListener { _ ->
binding.networkSettingLayout.visibility = View.INVISIBLE
2025-08-26 23:36:34 +09:00
binding.dialogButtonNetwork.requestFocus()
}
binding.btnApplyServer.setOnClickListener { _ ->
showCustomToast(applicationContext, "적용되었습니다.")
var param : JSONObject = JSONObject()
param.put("display_name", binding.editDisplayName.text)
param.put("account_name", binding.editSipId.text)
var password = binding.editSipPassword.text.toString()
if(password.isEmpty()) {
if (File(filesDir.absolutePath + "/accounts").exists()) {
val accounts = String(
Utils.getFileContents(filesDir.absolutePath + "/accounts")!!,
Charsets.UTF_8
).lines().toMutableList()
password = getPassword(accounts)
}
}
param.put("password", password)
param.put("server_address", binding.editSipServer.text)
var transport = "udp"
if(binding.radioTransportTcp.isChecked) {
transport = "tcp"
} else if(binding.radioTransportTls.isChecked) {
transport = "tls"
}
param.put("transport", transport)
var encrypt = "none"
if(binding.radioEncryptSrtp.isChecked) {
encrypt = "srtpo"
}
param.put("media_encryption", encrypt)
println("PARAM >> " + param.toString())
sendSettingByBroadcast("set_account", param.toString())
binding.serverSettingLayout.visibility = View.INVISIBLE
binding.dialogButtonServer.requestFocus()
}
binding.btnCancelServer.setOnClickListener { _ ->
binding.serverSettingLayout.visibility = View.INVISIBLE
binding.dialogButtonServer.requestFocus()
}
binding.btnPingTestServer.setOnClickListener { _ ->
Thread {
val rtts = Utils.ping(binding.editSipServer.text.toString(), 1)
if (rtts.isNotEmpty()) {
val avg = rtts.average()
runOnUiThread {
showCustomToast(applicationContext, "PING 성공하였습니다. RTT: ${"%.2f".format(avg)} ms")
}
} else {
runOnUiThread {
showCustomToast(applicationContext, "PING 실패하였습니다.")
}
}
}.start()
}
2025-08-26 23:36:34 +09:00
navUpServerList = HashMap<View, View>()
navDownServerList = HashMap<View, View>()
navUpServerList.put(binding.editDisplayName, binding.btnApplyServer)
navUpServerList.put(binding.editSipId, binding.editDisplayName)
navUpServerList.put(binding.editSipPassword, binding.editSipId)
navUpServerList.put(binding.editSipServer, binding.editSipPassword)
navUpServerList.put(binding.radioTransportUdp, binding.editSipServer)
navUpServerList.put(binding.radioTransportTcp, binding.editSipServer)
navUpServerList.put(binding.radioTransportTls, binding.editSipServer)
navUpServerList.put(binding.radioEncryptNone, binding.radioTransportUdp)
navUpServerList.put(binding.radioEncryptSrtp, binding.radioTransportUdp)
navUpServerList.put(binding.btnApplyServer, binding.radioEncryptNone)
navUpServerList.put(binding.btnCancelServer, binding.radioEncryptNone)
//navUpList.put(binding.btnApply, binding.radioDhcp)
navDownServerList.put(binding.editDisplayName, binding.editSipId)
navDownServerList.put(binding.editSipId, binding.editSipPassword)
navDownServerList.put(binding.editSipPassword, binding.editSipServer)
navDownServerList.put(binding.editSipServer, binding.radioTransportUdp)
navDownServerList.put(binding.radioTransportUdp, binding.radioEncryptNone)
navDownServerList.put(binding.radioTransportTcp, binding.radioEncryptNone)
navDownServerList.put(binding.radioTransportTls, binding.radioEncryptNone)
navDownServerList.put(binding.radioEncryptNone, binding.btnApplyServer)
navDownServerList.put(binding.radioEncryptSrtp, binding.btnApplyServer)
navDownServerList.put(binding.btnApplyServer, binding.editDisplayName)
navDownServerList.put(binding.btnCancelServer, binding.editDisplayName)
updateFieldsVisibility()
val radioGroup = findViewById<RadioGroup>(R.id.radioGroup)
radioGroup.setOnCheckedChangeListener { _, checkedId ->
val selected = findViewById<RadioButton>(checkedId)
selected.requestFocus()
updateFieldsVisibility()
}
2025-03-09 23:45:43 +09:00
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<LinearLayout>(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<Long> { 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<String>()
val shouldShow = mutableListOf<String>()
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) }
2025-08-26 23:36:34 +09:00
// else
// startBaresip()
2025-03-09 23:45:43 +09:00
}
}
2025-08-26 23:36:34 +09:00
startBaresip()
2025-03-09 23:45:43 +09:00
addVideoLayoutViews()
if (!BaresipService.isServiceRunning) {
if (File(filesDir.absolutePath + "/accounts").exists()) {
val accounts = String(
Utils.getFileContents(filesDir.absolutePath + "/accounts")!!,
Charsets.UTF_8
).lines().toMutableList()
2025-08-26 23:36:34 +09:00
//askPasswords(accounts)
2025-03-09 23:45:43 +09:00
} else {
// Baresip is started for the first time
requestPermissionsLauncher.launch(permissions)
}
}
//Utils.runShellOrder("sendserial /dev/ttyUSB10 115200 term '@123456'")
Utils.renderVfdString(",")
2025-03-09 23:45:43 +09:00
} // OnCreate
2025-08-26 23:36:34 +09:00
fun sendSettingByBroadcast(req : String, param : String) {
val responseIntent = Intent("kr.co.rito.ritosip.REQUEST")
responseIntent.putExtra("request", req)
responseIntent.putExtra("param", param)
responseIntent.setPackage("kr.co.rito.ritosip");
sendBroadcast(responseIntent)
}
2025-03-09 23:45:43 +09:00
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<TextView>(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.surfaceView.bringToFront()
2025-03-09 23:45:43 +09:00
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
} 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(
1,
1
)
prm2.leftMargin = 0
prm2.topMargin = 0
videoView.surfaceSelfView.layoutParams = prm2
} else if(BaresipService.displaySplitMode == BaresipService.DISPLAY_SPLIT_MODE_원거리_없음_근거리_최대) {
var prm1: RelativeLayout.LayoutParams =
RelativeLayout.LayoutParams(
1,
1
)
prm1.leftMargin = 0
prm1.topMargin = 0
videoView.surfaceView.layoutParams = prm1
var prm2: RelativeLayout.LayoutParams =
RelativeLayout.LayoutParams(
1920,
1080
)
prm2.leftMargin = 0
prm2.topMargin = 0
2025-03-09 23:45:43 +09:00
videoView.surfaceSelfView.layoutParams = prm2
}
}
private fun updateInfo() {
val view = findViewById<TextView>(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<TextView>(R.id.textViewDatetime)
val formattedDate = now.format(formatter)
viewDate.setText(formattedDate)
}
private fun updateDisplay() {
println("Update Display : ${BaresipService.connectedDisplayCount}, prevDisplayCount : ${BaresipService.prevConnectedDisplayCount}")
2025-03-09 23:45:43 +09:00
if(BaresipService.connectedDisplayCount < 2) {
if(BaresipService.prevConnectedDisplayCount != BaresipService.connectedDisplayCount) {
try {
presentation?.dismiss()
} catch(e : java.lang.Exception) {
}
try {
val parentView = videoView.surfaceView.parent
(parentView as ViewGroup).removeView(videoView.surfaceView)
} catch(e : java.lang.Exception) {
}
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) {
}
var prm: FrameLayout.LayoutParams =
FrameLayout.LayoutParams(
1920,
1080
)
prm.leftMargin = 0
prm.topMargin = 0
videoView.surfaceView.layoutParams = prm
videoLayout.addView(videoView.surfaceView)
}
2025-03-09 23:45:43 +09:00
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 {
presentation?.dismiss()
} catch(e : java.lang.Exception) {
}
try {
val parentView = videoView.surfaceView.parent
(parentView as ViewGroup).removeView(videoView.surfaceView)
} catch(e : java.lang.Exception) {
}
2025-03-09 23:45:43 +09:00
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) {
}
var useSecondScreenAsFar = false
if(BaresipService.farViewDisplayId == 1) {
useSecondScreenAsFar = false
} else if(BaresipService.farViewDisplayId == 2) {
useSecondScreenAsFar = true
}
2025-03-09 23:45:43 +09:00
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
if(useSecondScreenAsFar) {
//presentation?.setContentView(videoView.surfaceView)
presentation?.setContentView(R.layout.presentation_layout)
val presentationView = presentation?.window?.decorView?.findViewById<RelativeLayout>(R.id.presentationLayout)
presentationView?.addView(videoView.surfaceView)
2025-03-09 23:45:43 +09:00
presentationView?.addView(videoView.standbyView)
} else {
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<RelativeLayout>(R.id.presentationLayout)
try {
presentationView?.addView(videoView.surfaceSelfView)
} catch(e : java.lang.Exception) {
}
try {
presentationView?.addView(videoView.standbyView)
} catch(e : java.lang.Exception) {
}
2025-03-09 23:45:43 +09:00
}
if(useSecondScreenAsFar) {
var prm2: RelativeLayout.LayoutParams =
RelativeLayout.LayoutParams(
1920,
1080
)
prm2.leftMargin = 0
prm2.topMargin = 0
videoView.surfaceSelfView.layoutParams = prm2
videoLayout.addView(videoView.surfaceSelfView)
} else {
videoLayout.addView(videoView.surfaceView)
}
if(isCallExist()) {
videoView.surfaceSelfView.bringToFront()
videoView.surfaceView.bringToFront()
} else {
videoView.standbyView.bringToFront()
}
2025-03-09 23:45:43 +09:00
presentation?.show()
}
BaresipService.prevConnectedDisplayCount = BaresipService.connectedDisplayCount
2025-03-09 23:45:43 +09:00
}
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)
updateDisplayLayout()
2025-03-09 23:45:43 +09:00
} else {
var useSecondScreenAsFar = false
2025-03-09 23:45:43 +09:00
// DisplayManager 가져오기
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
val displays = displayManager.displays
if(BaresipService.farViewDisplayId == 1) {
useSecondScreenAsFar = false
} else if(BaresipService.farViewDisplayId == 2) {
useSecondScreenAsFar = true
}
2025-03-09 23:45:43 +09:00
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(R.layout.presentation_layout)
val presentationView = presentation?.window?.decorView?.findViewById<RelativeLayout>(R.id.presentationLayout)
presentationView?.addView(videoView.surfaceView)
presentationView?.addView(videoView.standbyView)
2025-03-09 23:45:43 +09:00
} 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<RelativeLayout>(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)
2025-08-26 23:36:34 +09:00
hb.visibility = View.INVISIBLE // by ritoseo
2025-03-09 23:45:43 +09:00
// 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'")
Log.w(TAG, "<RITO> Handling intent '$action'")
2025-03-09 23:45:43 +09:00
val ev = action.split(",")
when (ev[0]) {
"no network" -> { // 네트워크 안잡힌 경우
/*
Utils.alertView(this, getString(R.string.notice),
getString(R.string.no_network))
*/
Utils.renderVfdString("NO,NETWORK")
2025-03-09 23:45:43 +09:00
return
}
/* Added by ritoseo */
"network available" -> {
val tv = findViewById<TextView>(R.id.textViewHeaderIp)
tv.setText(intent.getStringExtra("address")!!)
Utils.renderVfdString(tv.text.toString())
2025-03-09 23:45:43 +09:00
}
"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()
}
"camera connected" -> {
if(!isCallExist()) {
startSelfMoitoringCamera()
}
}
"camera removed" -> {
if(!isCallExist()) {
stopSelfMoitoringCamera()
resetSelfMoitoringCamera()
}
}
2025-03-09 23:45:43 +09:00
/********************/
"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") {
stopSelfMoitoringCamera() // 모니터링 셀프 뷰 종료
Handler(Looper.getMainLooper()).postDelayed({
videoButton.performClick()
}, 1000)
Handler(Looper.getMainLooper()).postDelayed({
if(BaresipService.connectedCameraCount == 0) {
println("Answer Call setVideoFake 실행")
call.setVideoFake()
}
//call.setVideoDirection(Api.SDP_RECVONLY)
}, 1500)
2025-03-09 23:45:43 +09:00
}
}
"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
//val BTN_MENU = 58
val SCANCODE_OPTION = 357 //MENU키 -> OPTION키로 매핑
val SCANCODE_RED = 398
val SCANCODE_GREEN = 399
2025-03-09 23:45:43 +09:00
//println("onKeyDown : ${keyCode}")
//println("onKeyDown2 : ${event?.scanCode}")
2025-03-09 23:45:43 +09:00
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()
2025-03-09 23:45:43 +09:00
return true
}
KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_3, KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_6,
KeyEvent.KEYCODE_7, KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_9
-> {
callUri.setText(callUri.text.toString() + (keyCode - KeyEvent.KEYCODE_0).toString())
}
KeyEvent.KEYCODE_DEL -> {
val editable = callUri.text
val length = editable.length
if (length > 0) {
editable.delete(length - 1, length)
}
}
KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DPAD_CENTER -> {
if(BaresipService.sourceSelect == 1) {
BaresipService.sourceSelect = 2
} else {
BaresipService.sourceSelect = 1
}
return true
}
2025-03-09 23:45:43 +09:00
BTN_MISC -> {
return true
}
}
when (event?.scanCode) {
SCANCODE_RED -> {
if(!isCallExist()) {
startSelfMoitoringCamera()
}
}
SCANCODE_GREEN -> {
if(!isCallExist()) {
stopSelfMoitoringCamera()
}
}
SCANCODE_OPTION -> {
2025-08-26 23:36:34 +09:00
// val dialog = findViewById<FrameLayout>(R.id.dialogLayout)
// if(dialog.isVisible) {
// dialog.visibility = View.INVISIBLE
// } else {
// dialog.visibility = View.VISIBLE
// findViewById<AppCompatButton>(R.id.dialogButtonImg1).requestFocus()
// }
// val dialog = findViewById<FrameLayout>(R.id.settingLayout)
// if (dialog.isVisible) {
// dialog.visibility = View.INVISIBLE
// } else {
// dialog.bringToFront()
// dialog.visibility = View.VISIBLE
// findViewById<AppCompatButton>(R.id.dialogButtonLayout).requestFocus()
// }
if(binding.layoutSettingLayout.visibility == View.VISIBLE ||
binding.networkSettingLayout.visibility == View.VISIBLE ||
binding.serverSettingLayout.visibility == View.VISIBLE ||
binding.volumeSettingLayout.visibility == View.VISIBLE)
return true
if (Call.inCall()) {
val dialog = findViewById<FrameLayout>(R.id.settingLayoutInCall)
if (dialog.isVisible) {
dialog.visibility = View.INVISIBLE
println("settingLayoutInCall 비활성화")
} else {
dialog.bringToFront()
dialog.visibility = View.VISIBLE
findViewById<AppCompatButton>(R.id.dialogButtonLayoutInCall).requestFocus()
println("settingLayoutInCall 활성화")
}
} else {
2025-08-26 23:36:34 +09:00
val dialog = findViewById<FrameLayout>(R.id.settingLayout)
if (dialog.isVisible) {
dialog.visibility = View.INVISIBLE
} else {
dialog.bringToFront()
dialog.visibility = View.VISIBLE
findViewById<AppCompatButton>(R.id.dialogButtonLayout).requestFocus()
}
}
2025-08-26 23:36:34 +09:00
// val dialog = findViewById<FrameLayout>(R.id.dialogBase)
// val customButton = LayoutInflater.from(this).inflate(R.layout.image_button, null)
// dialog.addView(customButton)
return true
}
}
2025-03-09 23:45:43 +09:00
return super.onKeyDown(keyCode, event)
}
private fun handleServiceEvent(event: String, params: ArrayList<Any>) {
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()
}
updateDisplay()
BaresipService.supportedCameras = Utils.supportedCameras(applicationContext).isNotEmpty()
2025-03-09 23:45:43 +09:00
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" -> {
val ua = BaresipService.uas[0]
val call = ua.currentCall()
if(call != null) {
val callNo = call.peerUri.split("@")[0].filter { it.isDigit() }
Utils.renderVfdString("CALL,${callNo}")
}
2025-03-09 23:45:43 +09:00
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()
Utils.renderVfdString(binding.textViewHeaderIp.text.toString())
BaresipService.isMicMuted = false
BaresipService.isVideoMuted = false
2025-03-09 23:45:43 +09:00
}
"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()
}
}
2025-08-26 23:36:34 +09:00
private fun getPassword(accounts: MutableList<String>) : String {
if (accounts.isNotEmpty()) {
val account = accounts.removeAt(0)
val params = account.substringAfter(">")
if ((Utils.paramValue(params, "auth_user") != "") &&
(Utils.paramValue(params, "auth_pass") != "")) {
return Utils.paramValue(params, "auth_pass").trim('"')
}
}
return ""
}
2025-03-09 23:45:43 +09:00
private fun askPasswords(accounts: MutableList<String>) {
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)
}
2025-03-09 23:45:43 +09:00
}
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) {
stopSelfMoitoringCamera() // 모니터링 Self 카메라 멈춤
2025-03-09 23:45:43 +09:00
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)
2025-08-26 23:36:34 +09:00
hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
2025-03-09 23:45:43 +09:00
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.visibility = View.INVISIBLE // modified by ritoseo
2025-03-09 23:45:43 +09:00
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.visibility = View.INVISIBLE // modified by ritoseo
2025-03-09 23:45:43 +09:00
callVideoButton.isEnabled = true
2025-08-26 23:36:34 +09:00
binding.callBackground.visibility = View.GONE
2025-03-09 23:45:43 +09:00
hangupButton.visibility = View.INVISIBLE
2025-08-26 23:36:34 +09:00
binding.callBackground.visibility = View.GONE
2025-03-09 23:45:43 +09:00
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
2025-08-26 23:36:34 +09:00
binding.callBackground.visibility = View.VISIBLE
hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
2025-03-09 23:45:43 +09:00
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)
2025-08-26 23:36:34 +09:00
binding.callBackground.visibility = View.GONE
2025-03-09 23:45:43 +09:00
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)
2025-08-26 23:36:34 +09:00
binding.callBackground.visibility = View.VISIBLE
hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
2025-03-09 23:45:43 +09:00
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<Intent>? = null
var activityAor = ""
lateinit var videoView: VideoView
}
init {
if (!BaresipService.libraryLoaded) {
Log.i(TAG, "Loading baresip library")
System.loadLibrary("baresip")
BaresipService.libraryLoaded = true
}
}
}