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 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 import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.widget.AppCompatButton import androidx.camera.view.PreviewView 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 import androidx.lifecycle.Observer import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.tutpro.baresip.plus.Utils.getAppVersion import com.tutpro.baresip.plus.Utils.showSnackBar import com.tutpro.baresip.plus.databinding.ActivityMainBinding import org.json.JSONObject 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 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 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() } } } class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var defaultLayout: RelativeLayout private lateinit var videoLayout: RelativeLayout //private lateinit var videoView: VideoView private lateinit var callTitle: TextView private lateinit var callTimer: Chronometer private lateinit var callUri: AutoCompleteTextView private lateinit var securityButton: ImageButton private lateinit var videoSecurityButton: ImageButton private lateinit var diverter: LinearLayout private lateinit var diverterUri: TextView private lateinit var callButton: ImageButton private lateinit var callVideoButton: ImageButton private lateinit var hangupButton: ImageButton private lateinit var answerButton: ImageButton private lateinit var answerVideoButton: ImageButton private lateinit var rejectButton: ImageButton private lateinit var callControl: RelativeLayout private lateinit var holdButton: ImageButton private lateinit var transferButton: ImageButton private lateinit var videoButton: ImageButton private lateinit var voicemailButton: ImageButton private lateinit var voicemailButtonSpace: Space private lateinit var contactsButton: ImageButton private lateinit var messagesButton: ImageButton private lateinit var callsButton: ImageButton private lateinit var dialpadButton: ImageButton private lateinit var dtmf: EditText private var dtmfWatcher: TextWatcher? = null private lateinit var infoButton: ImageButton private lateinit var onHoldNotice: TextView private lateinit var videoOnHoldNotice: TextView private lateinit var uaAdapter: UaSpinnerAdapter private lateinit var aorSpinner: Spinner private lateinit var imm: InputMethodManager private lateinit var nm: NotificationManager private lateinit var am: AudioManager private lateinit var kgm: KeyguardManager private lateinit var screenEventReceiver: BroadcastReceiver private lateinit var serviceEventObserver: Observer> private var recIcon: MenuItem? = null private var micIcon: MenuItem? = null private var speakerIcon: MenuItem? = null private lateinit var speakerButton: ImageButton private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var requestPermissionLauncher: ActivityResultLauncher private lateinit var requestPermissionsLauncher: ActivityResultLauncher> private lateinit var accountsRequest: ActivityResultLauncher private lateinit var chatRequests: ActivityResultLauncher private lateinit var configRequest: ActivityResultLauncher private lateinit var backupRequest: ActivityResultLauncher private lateinit var restoreRequest: ActivityResultLauncher private lateinit var logcatRequest: ActivityResultLauncher private lateinit var contactsRequest: ActivityResultLauncher private lateinit var callsRequest: ActivityResultLauncher private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener private lateinit var permissions: Array private lateinit var dialogImgBtn1: AppCompatButton private lateinit var dialogImgBtn2: AppCompatButton private lateinit var callStartButton: AppCompatButton private lateinit var callHistoryButton: AppCompatButton private lateinit var settingButton: AppCompatButton 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() 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 lateinit var navUpListAlter : HashMap lateinit var navDownList : HashMap lateinit var navDownListAlter : HashMap lateinit var navUpServerList : HashMap lateinit var navDownServerList : HashMap private var cameraProvider: ProcessCameraProvider? = null @OptIn(ExperimentalCamera2Interop::class) private fun cameraSelectorById(targetId: String): CameraSelector { return CameraSelector.Builder() .addCameraFilter { cameraInfos: List -> 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 // 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 } } 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 } } } } 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 || 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 // } 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 } 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(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(R.id.editIp).requestFocus() // findViewById(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() } @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() // ) 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() Utils.propertySet("sys.ritosip.version", getAppVersion(this)) // 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 // 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() } 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()) } 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()) } binding.seekbarMicVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { // progress: 현재 값 // fromUser: 사용자가 직접 움직였는지 여부 val currentValue = progress println("현재 MIC 값: $currentValue") 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?) { // 터치 끝날 때 호출 } }) 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() } } 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() navUpListAlter = HashMap() navDownList = HashMap() navDownListAlter = HashMap() 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 = HashMap() 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 = HashMap() 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 } binding.dialogButtonNetwork.requestFocus() } binding.btnCancel.setOnClickListener { _ -> binding.networkSettingLayout.visibility = View.INVISIBLE 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() } navUpServerList = HashMap() navDownServerList = HashMap() 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(R.id.radioGroup) radioGroup.setOnCheckedChangeListener { _, checkedId -> val selected = findViewById(checkedId) selected.requestFocus() updateFieldsVisibility() } serviceEventObserver = Observer { val event = it.getContentIfNotHandled() Log.d(TAG, "Observed event $event") if (event != null && BaresipService.serviceEvents.isNotEmpty()) { val first = BaresipService.serviceEvents.removeAt(0) handleServiceEvent(first.event, first.params) } } BaresipService.serviceEvent.observeForever(serviceEventObserver) screenEventReceiver = object : BroadcastReceiver() { override fun onReceive(contxt: Context, intent: Intent) { if (kgm.isKeyguardLocked) { Log.d(TAG, "Screen on when locked") this@MainActivity.setShowWhenLocked(Call.inCall()) } } } if(Utils.propertyGet("sys.rito.debug") == "1") { binding.toolbar.visibility = View.VISIBLE findViewById(R.id.header_bar).visibility = View.GONE } this.registerReceiver(screenEventReceiver, IntentFilter().apply { addAction(Intent.ACTION_SCREEN_ON) }) if (Build.VERSION.SDK_INT >= 31) { comDevChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device -> if (device != null) { Log.d(TAG, "Com device changed to type ${device.type} in mode ${am.mode}") setSpeakerButtonAndIcon() } } am.addOnCommunicationDeviceChangedListener(mainExecutor, comDevChangedListener) } uaAdapter = UaSpinnerAdapter(applicationContext, BaresipService.uas) aorSpinner.adapter = uaAdapter aorSpinner.setSelection(-1) aorSpinner.tag = "" aorSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { // Have to allow NULL view, since sometimes when onItemSelected is called, view is NULL. // Haven't found any explanation why this can happen. override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { Log.d(TAG, "aorSpinner selecting $position") if (position < BaresipService.uas.size) { val ua = BaresipService.uas[position] val acc = ua.account aorSpinner.tag = acc.aor showCall(ua) updateIcons(acc) } } override fun onNothingSelected(parent: AdapterView<*>) { Log.d(TAG, "Nothing selected") } } val registrationObserver = Observer { uaAdapter.notifyDataSetChanged() } BaresipService.registrationUpdate.observe(this, registrationObserver) accountsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { uaAdapter.notifyDataSetChanged() spinToAor(activityAor) if (aorSpinner.tag != "") updateIcons(Account.ofAor(aorSpinner.tag.toString())!!) if (BaresipService.isServiceRunning) { baresipService.action = "Update Notification" startService(baresipService) } } accountRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) val ua = UserAgent.ofAor(activityAor)!! updateIcons(ua.account) if (it.resultCode == Activity.RESULT_OK) if (BaresipService.aorPasswords[activityAor] == NO_AUTH_PASS) askPassword(getString(R.string.authentication_password), ua) } aorSpinner.setOnTouchListener { view, event -> if (event.action == MotionEvent.ACTION_DOWN) { if (aorSpinner.selectedItemPosition == -1) { val i = Intent(this@MainActivity, AccountsActivity::class.java) val b = Bundle() b.putString("aor", "") i.putExtras(b) accountsRequest.launch(i) true } else { if ((event.x - view.left) < 100) { val i = Intent(this@MainActivity, AccountActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) accountRequest!!.launch(i) true } else { BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri = callUri.text.toString() false } } } else { // view.performClick() false } } aorSpinner.setOnLongClickListener { if (aorSpinner.selectedItemPosition != -1) { val ua = UserAgent.ofAor(aorSpinner.tag.toString()) if (ua != null) { val acc = ua.account if (Api.account_regint(acc.accp) > 0) { Api.account_set_regint(acc.accp, 0) Api.ua_unregister(ua.uap) } else { Api.account_set_regint(acc.accp, acc.configuredRegInt) Api.ua_register(ua.uap) } acc.regint = Api.account_regint(acc.accp) AccountsActivity.saveAccounts() } } true } callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) callUri.threshold = 2 callUri.setOnFocusChangeListener { view, b -> if (b) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } } callUri.setOnClickListener { view -> imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } securityButton.setOnClickListener { when (securityButton.tag) { R.drawable.unlocked -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.call_not_secure)) } R.drawable.locked_yellow -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.peer_not_verified)) } R.drawable.locked_green -> { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.info) setMessage(getString(R.string.call_is_secure)) setPositiveButton(getString(R.string.unverify)) { dialog, _ -> val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) { Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed") } else { securityButton.setImageResource(R.drawable.locked_yellow) securityButton.tag = R.drawable.locked_yellow } } dialog.dismiss() } setNeutralButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } } } callButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO))) makeCall("voice") else Toast.makeText(applicationContext, getString(R.string.no_calls), Toast.LENGTH_SHORT).show() } } callVideoButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO, CAMERA))) makeCall("video") else Toast.makeText( applicationContext, getString(R.string.no_video_calls), Toast.LENGTH_SHORT ).show() } } hangupButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (Build.VERSION.SDK_INT < 31) { if (callRunnable != null) { callHandler.removeCallbacks(callRunnable!!) callRunnable = null BaresipService.abandonAudioFocus(applicationContext) showCall(ua) return@setOnClickListener } } else { if (audioModeChangedListener != null) { am.removeOnModeChangedListener(audioModeChangedListener!!) audioModeChangedListener = null BaresipService.abandonAudioFocus(applicationContext) showCall(ua) return@setOnClickListener } } val aor = ua.account.aor val uaCalls = ua.calls() if (uaCalls.size > 0) { val call = uaCalls[uaCalls.size - 1] val callp = call.callp Log.d(TAG, "AoR $aor hanging up call $callp with ${callUri.text}") hangupButton.isEnabled = false Api.ua_hangup(ua.uap, callp, 0, "") } } answerButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() ?: return@setOnClickListener Log.d(TAG, "AoR ${ua.account.aor} answering call from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false call.setMediaDirection(Api.SDP_SENDRECV, Api.SDP_INACTIVE) call.disableVideoStream(true) val intent = Intent(this@MainActivity, BaresipService::class.java) intent.action = "Call Answer" intent.putExtra("uap", ua.uap) intent.putExtra("callp", call.callp) startService(intent) } answerVideoButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.calls("in")[0] Log.d(TAG, "AoR $aor answering video call ${call.callp} from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false val videoDir = if (Utils.isCameraAvailable(this)) Api.SDP_SENDRECV else Api.SDP_RECVONLY call.setMediaDirection(Api.SDP_SENDRECV, videoDir) val intent = Intent(this@MainActivity, BaresipService::class.java) intent.action = "Call Answer" intent.putExtra("uap", ua.uap) intent.putExtra("callp", call.callp) startService(intent) } rejectButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.currentCall()!! val callp = call.callp Log.d(TAG, "AoR $aor rejecting call $callp from ${callUri.text}") answerButton.isEnabled = false answerVideoButton.isEnabled = false rejectButton.isEnabled = false call.rejected = true Api.ua_hangup(ua.uap, callp, 486, "Busy Here") } holdButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor val call = ua.currentCall()!! if (call.onhold) { Log.d(TAG, "AoR $aor resuming call ${call.callp} with ${callUri.text}") call.resume() call.onhold = false holdButton.setImageResource(R.drawable.call_hold) } else { Log.d(TAG, "AoR $aor holding call ${call.callp} with ${callUri.text}") call.hold() call.onhold = true holdButton.setImageResource(R.drawable.resume) } } transferButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null ) { if (call.onHoldCall != null) { if (!call.executeTransfer()) Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.transfer_failed))) } else { makeTransfer(ua) } } } infoButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { val stats = call.stats("audio") if (stats != "") { val parts = stats.split(",") as ArrayList if (parts[2] == "0/0") { parts[2] = "?/?" parts[3] = "?/?" parts[4] = "?/?" } val codecs = call.audioCodecs() val duration = call.duration() val txCodec = codecs.split(',')[0].split("/") val rxCodec = codecs.split(',')[1].split("/") Utils.alertView(this, getString(R.string.call_info), "${String.format(getString(R.string.duration), duration)}\n" + "${getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/" + "${rxCodec[0]} ch ${rxCodec[2]}\n" + "${String.format(getString(R.string.rate), parts[0])}\n" + "${String.format(getString(R.string.average_rate), parts[1])}\n" + "${getString(R.string.packets)}: ${parts[2]}\n" + "${getString(R.string.lost)}: ${parts[3]}\n" + String.format(getString(R.string.jitter), parts[4])) } else { Utils.alertView(this, getString(R.string.call_info), getString(R.string.call_info_not_available)) } } } dtmf.setOnFocusChangeListener { view, hasFocus -> if (hasFocus) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } } dtmf.setOnClickListener { view -> imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } voicemailButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val acc = ua.account if (acc.vmUri != "") { val dialogClickListener = DialogInterface.OnClickListener { _, which -> when (which) { DialogInterface.BUTTON_POSITIVE -> { val i = Intent(this, MainActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP i.putExtra("action", "call") i.putExtra("uap", ua.uap) i.putExtra("peer", acc.vmUri) startActivity(i) } DialogInterface.BUTTON_NEGATIVE -> { } } } with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.voicemail_messages) setMessage(acc.vmMessages(this@MainActivity)) setPositiveButton(getString(R.string.listen), dialogClickListener) setNeutralButton(getString(R.string.cancel), dialogClickListener) show() } } } } contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) } contactsButton.setOnClickListener { val i = Intent(this@MainActivity, ContactsActivity::class.java) val b = Bundle() if (aorSpinner.selectedItemPosition >= 0) b.putString("aor", aorSpinner.tag.toString()) else b.putString("aor", "") i.putExtras(b) contactsRequest.launch(i) } chatRequests = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) updateIcons(Account.ofAor(activityAor)!!) } messagesButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, ChatsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) b.putString("peer", resumeUri) i.putExtras(b) chatRequests.launch(i) } } callsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { spinToAor(activityAor) callsButton.setImageResource(R.drawable.calls) } callsButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, CallsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) callsRequest.launch(i) } } dialpadButton.tag = "off" dialpadButton.setOnClickListener { if (dialpadButton.tag == "off") { callUri.inputType = InputType.TYPE_CLASS_PHONE dialpadButton.setImageResource(R.drawable.dialpad_on) dialpadButton.tag = "on" //Log.d(TAG, "Screen ${Utils.getScreenOrientation(applicationContext)}") //val path = BaresipService.downloadsPath + "/video.mp4" //Utils.ffmpegExecute("-video_size hd720 -f android_camera -camera_index 1 -i anything -r 10 -t 5 -y $path") } else { callUri.inputType = InputType.TYPE_CLASS_TEXT + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS dialpadButton.setImageResource(R.drawable.dialpad_off) dialpadButton.tag = "off" } } videoButton.setOnClickListener { videoButton.isClickable = false videoButton.setImageResource(R.drawable.video_pending) Handler(Looper.getMainLooper()).postDelayed({ val call = Call.call("connected") if (call != null) { val dir = call.videoRequest if (dir != 0) { call.videoRequest = 0 call.setVideoDirection(dir) } else { if (Utils.isCameraAvailable(this)) call.setVideoDirection(Api.SDP_SENDRECV) else call.setVideoDirection(Api.SDP_RECVONLY) } imm.hideSoftInputFromWindow(dtmf.windowToken, 0) } }, 250) } configRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if ((it.data != null) && it.data!!.hasExtra("restart")) { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.restart_request) setMessage(getString(R.string.config_restart)) setPositiveButton(getText(R.string.restart)) { dialog, _ -> dialog.dismiss() quitRestart(true) } setNeutralButton(getText(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } val displayTheme = Preferences(applicationContext).displayTheme if (displayTheme != AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.setDefaultNightMode(displayTheme) delegate.applyDayNight() } } backupRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> downloadsOutputUri = uri askPassword(getString(R.string.encrypt_password)) } } restoreRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> downloadsInputUri = uri askPassword(getString(R.string.decrypt_password)) } } logcatRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) it.data?.data?.also { uri -> try { val out = contentResolver.openOutputStream(uri) val process = Runtime.getRuntime().exec("logcat -d --pid=${Process.myPid()}") val bufferedReader = process.inputStream.bufferedReader() bufferedReader.forEachLine { line -> out!!.write(line.toByteArray()) out.write('\n'.code.toByte().toInt()) } out!!.close() } catch (e: Exception) { Log.e(TAG, "Failed to write logcat to file") } } } swipeRefresh.setOnTouchListener(object : OnSwipeTouchListener(this@MainActivity) { override fun onSwipeLeft() { super.onSwipeLeft() if (BaresipService.uas.size > 0) { val curPos = aorSpinner.selectedItemPosition val newPos = if (curPos == -1) 0 else (curPos + 1) % BaresipService.uas.size if (curPos != newPos) { aorSpinner.setSelection(newPos) showCall(BaresipService.uas[newPos]) } } } override fun onSwipeRight() { super.onSwipeRight() if (BaresipService.uas.size > 0) { val curPos = aorSpinner.selectedItemPosition val newPos = when (curPos) { -1 -> 0 0 -> BaresipService.uas.size - 1 else -> curPos - 1 } if (curPos != newPos) { aorSpinner.setSelection(newPos) showCall(BaresipService.uas[newPos]) } } } }) swipeRefresh.setOnRefreshListener { if (BaresipService.uas.size > 0) { if (aorSpinner.selectedItemPosition == -1) aorSpinner.setSelection(0) val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (ua.account.regint > 0) Api.ua_register(ua.uap) } swipeRefresh.isRefreshing = false } baresipService = Intent(this@MainActivity, BaresipService::class.java) atStartup = intent.hasExtra("onStartup") when (intent?.action) { ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> if (BaresipService.isServiceRunning) callAction(intent.data, if (intent?.action == ACTION_CALL) "call" else "dial") else BaresipService.callActionUri = intent.data.toString() .replace("tel:%2B", "tel:+") } permissions = if (BaresipService.supportedCameras) { if (Build.VERSION.SDK_INT >= 33) arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT) else if (Build.VERSION.SDK_INT >= 31) arrayOf(RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT) else arrayOf(RECORD_AUDIO, CAMERA) } else { if (Build.VERSION.SDK_INT >= 33) arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, BLUETOOTH_CONNECT) else if (Build.VERSION.SDK_INT >= 31) arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT) else arrayOf(RECORD_AUDIO) } requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {} requestPermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { val denied = mutableListOf() val shouldShow = mutableListOf() it.forEach { permission -> if (!permission.value) { denied.add(permission.key) if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.key)) shouldShow.add(permission.key) } } if (denied.contains(POST_NOTIFICATIONS) && !shouldShow.contains(POST_NOTIFICATIONS)) { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(getString(R.string.notice)) setMessage(getString(R.string.no_notifications)) setPositiveButton(getString(R.string.ok)) { _, _ -> quitRestart(false) } show() } } else { if (shouldShow.isNotEmpty()) Utils.alertView(this, getString(R.string.permissions_rationale), if (CAMERA in permissions) getString(R.string.audio_and_video_permissions) else getString(R.string.audio_permissions) ) { requestPermissionsLauncher.launch(permissions) } // else // startBaresip() } } startBaresip() addVideoLayoutViews() if (!BaresipService.isServiceRunning) { if (File(filesDir.absolutePath + "/accounts").exists()) { val accounts = String( Utils.getFileContents(filesDir.absolutePath + "/accounts")!!, Charsets.UTF_8 ).lines().toMutableList() //askPasswords(accounts) } else { // Baresip is started for the first time requestPermissionsLauncher.launch(permissions) } } //Utils.runShellOrder("sendserial /dev/ttyUSB10 115200 term '@123456'") Utils.renderVfdString(",") } // OnCreate 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) } override fun onStart() { super.onStart() Log.d(TAG, "Main onStart") val action = intent.getStringExtra("action") if (action != null) { // MainActivity was not visible when call, message, or transfer request came in intent.removeExtra("action") handleIntent(intent, action) } } override fun onResume() { super.onResume() Log.d(TAG, "Main onResume with action '$resumeAction'") nm.cancelAll() BaresipService.isMainVisible = true when (resumeAction) { "call show" -> { handleServiceEvent ("call incoming", arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) } "call answer" -> { answerButton.performClick() showCall(resumeCall!!.ua) } "call missed" -> { callsButton.performClick() } "call reject" -> rejectButton.performClick() "call" -> { callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) callButton.performClick() } "dial" -> { callUri.setText(BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri) } "call transfer", "transfer show", "transfer accept" -> handleServiceEvent("$resumeAction,$resumeUri", arrayListOf(resumeCall!!.ua.uap, resumeCall!!.callp)) "message", "message show", "message reply" -> handleServiceEvent(resumeAction, arrayListOf(resumeUap, resumeUri)) else -> { val incomingCall = Call.call("incoming") if (incomingCall != null) { spinToAor(incomingCall.ua.account.aor) } else { restoreActivities() if (BaresipService.uas.size > 0) { if (aorSpinner.selectedItemPosition == -1) { if (Call.inCall()) spinToAor(Call.calls()[0].ua.account.aor) else { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } } } } uaAdapter.notifyDataSetChanged() if (BaresipService.uas.size > 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] showCall(ua) updateIcons(ua.account) } } } resumeAction = "" val tv = findViewById(R.id.textViewHeaderIp) tv.setText(BaresipService.deviceIpAddress) } override fun onPause() { super.onPause() Log.d(TAG, "Main onPause") Utils.addActivity("main") BaresipService.isMainVisible = false callTimer.stop() saveCallUri() } override fun onStop() { super.onStop() Log.d(TAG, "Main onStop") } override fun onDestroy() { super.onDestroy() Log.d(TAG, "Main onDestroy") this.unregisterReceiver(screenEventReceiver) if (Build.VERSION.SDK_INT >= 31) am.removeOnCommunicationDeviceChangedListener(comDevChangedListener) BaresipService.serviceEvent.removeObserver(serviceEventObserver) BaresipService.serviceEvents.clear() BaresipService.activities.clear() } var prevLayoutShow = false private fun switchVideoLayout(show : Boolean) { if(show == prevLayoutShow) { return } prevLayoutShow = show if(BaresipService.connectedDisplayCount < 2) return println("switchVideoLayout : ${show}") if(show) { // var prm: FrameLayout.LayoutParams = // FrameLayout.LayoutParams( // 1920, // 1080 // ) // prm.leftMargin = 0 // prm.topMargin = 0 // videoView.surfaceSelfView.layoutParams = prm // try { // val parentView = videoView.surfaceSelfView.parent // (parentView as ViewGroup).removeView(videoView.surfaceSelfView) // } catch(e : java.lang.Exception) { // } // presentation?.setContentView(videoView.surfaceSelfView) videoView.surfaceView.bringToFront() 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 videoView.surfaceSelfView.layoutParams = prm2 } } private fun updateInfo() { val view = findViewById(R.id.textViewDeviceName) view.setText(BaresipService.deviceName) val now = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일(E요일) a h시 m분", Locale.KOREAN) val viewDate = findViewById(R.id.textViewDatetime) val formattedDate = now.format(formatter) viewDate.setText(formattedDate) } private fun updateDisplay() { println("Update Display : ${BaresipService.connectedDisplayCount}, prevDisplayCount : ${BaresipService.prevConnectedDisplayCount}") 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) } 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) { } 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 } 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(R.id.presentationLayout) presentationView?.addView(videoView.surfaceView) 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(R.id.presentationLayout) try { presentationView?.addView(videoView.surfaceSelfView) } catch(e : java.lang.Exception) { } try { presentationView?.addView(videoView.standbyView) } catch(e : java.lang.Exception) { } } 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() } presentation?.show() } BaresipService.prevConnectedDisplayCount = BaresipService.connectedDisplayCount } 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() } else { var useSecondScreenAsFar = false // 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 } 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(R.id.presentationLayout) presentationView?.addView(videoView.surfaceView) presentationView?.addView(videoView.standbyView) } else { //presentation?.setContentView(videoView.surfaceSelfView) var prm2: FrameLayout.LayoutParams = FrameLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 0 prm2.topMargin = 0 videoView.surfaceSelfView.layoutParams = prm2 //videoLayout.addView(videoView.surfaceSelfView) presentation?.setContentView(R.layout.presentation_layout) val presentationView = presentation?.window?.decorView?.findViewById(R.id.presentationLayout) presentationView?.addView(videoView.surfaceSelfView) presentationView?.addView(videoView.standbyView) //presentation?.setContentView(videoView.standbyView) } presentation?.show() } else { var prm2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams( 1920, 1080 ) prm2.leftMargin = 1919 prm2.topMargin = 1079 videoView.surfaceSelfView.layoutParams = prm2 videoLayout.addView(videoView.surfaceSelfView) //videoView.surfaceSelfView.visibility = View.INVISIBLE } if(useSecondScreenAsFar) { videoLayout.addView(videoView.surfaceSelfView) } else { videoLayout.addView(videoView.surfaceView) } } // Video Button val vb = ImageButton(this) vb.id = View.generateViewId() vb.setImageResource(R.drawable.video_off) vb.setBackgroundResource(0) var prm: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) prm.marginStart = 15 prm.bottomMargin = 15 vb.layoutParams = prm vb.setOnClickListener { Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE) } videoLayout.addView(vb) vb.visibility = View.INVISIBLE // by ritoseo // Camera Button val cb = ImageButton(this) if (!Utils.isCameraAvailable(this)) cb.visibility = View.INVISIBLE cb.visibility = View.INVISIBLE // by ritoseo cb.id = View.generateViewId() cb.setImageResource(R.drawable.camera_front) cb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT ) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ALIGN_PARENT_TOP) prm.marginStart = 15 prm.topMargin = 15 cb.layoutParams = prm cb.setOnClickListener { val call = Call.call("connected") if (call != null) { if (call.setVideoSource(!BaresipService.cameraFront) != 0) Log.w(TAG, "Failed to set video source") else BaresipService.cameraFront = !BaresipService.cameraFront if (BaresipService.cameraFront) cb.setImageResource(R.drawable.camera_front) else cb.setImageResource(R.drawable.camera_rear) } } videoLayout.addView(cb) // Snapshot Button if ((Build.VERSION.SDK_INT >= 29) || Utils.checkPermissions(this, arrayOf(WRITE_EXTERNAL_STORAGE))) { val sb = ImageButton(this) sb.setImageResource(R.drawable.snapshot) sb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.BELOW, cb.id) prm.marginStart = 15 prm.topMargin= 24 sb.layoutParams = prm sb.setOnClickListener { val sdf = SimpleDateFormat("yyyyMMdd_hhmmss", Locale.getDefault()) val fileName = "IMG_" + sdf.format(Date()) + ".png" val filePath = BaresipService.filesPath + "/" + fileName if (Api.cmd_exec("snapshot_recv $filePath") != 0) Log.e(TAG, "Command 'snapshot_recv $filePath' failed") else MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) } videoLayout.addView(sb) sb.visibility = View.INVISIBLE // by ritoseo } // Lock Button videoSecurityButton = ImageButton(this) videoSecurityButton.visibility = View.INVISIBLE videoSecurityButton.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT) prm.addRule(RelativeLayout.ABOVE, vb.id) prm.marginStart = 15 prm.bottomMargin= 24 videoSecurityButton.layoutParams = prm videoSecurityButton.setOnClickListener { when (securityButton.tag) { R.drawable.unlocked -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.call_not_secure)) } R.drawable.locked_yellow -> { Utils.alertView(this, getString(R.string.alert), getString(R.string.peer_not_verified)) } R.drawable.locked_green -> { with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(R.string.info) setMessage(getString(R.string.call_is_secure)) setPositiveButton(getString(R.string.unverify)) { dialog, _ -> val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val call = ua.currentCall() if (call != null) { if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) { Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed") } else { securityButton.tag = R.drawable.locked_yellow videoSecurityButton.setImageResource(R.drawable.locked_video_yellow) } } dialog.dismiss() } setNeutralButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } } } videoLayout.addView(videoSecurityButton) videoSecurityButton.visibility = View.INVISIBLE // by ritoseo // Speaker Button speakerButton = ImageButton(this) speakerButton.id = View.generateViewId() speakerButton.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.ALIGN_PARENT_TOP) prm.marginEnd = 15 prm.topMargin = 15 speakerButton.layoutParams = prm speakerButton.setOnClickListener { Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am) setSpeakerButtonAndIcon() } setSpeakerButtonAndIcon() videoLayout.addView(speakerButton) speakerButton.visibility = View.INVISIBLE // by ritoseo // Mic Button val mb = ImageButton(this) mb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.BELOW, speakerButton.id) prm.marginEnd = 15 prm.topMargin= 24 mb.layoutParams = prm if (BaresipService.isMicMuted) mb.setImageResource(R.drawable.mic_off_button) else mb.setImageResource(R.drawable.mic_on_button) mb.setOnClickListener { BaresipService.isMicMuted = !BaresipService.isMicMuted if (BaresipService.isMicMuted) { mb.setImageResource(R.drawable.mic_off_button) Api.calls_mute(true) } else { mb.setImageResource(R.drawable.mic_on_button) Api.calls_mute(false) } } videoLayout.addView(mb) mb.visibility = View.INVISIBLE // by ritoseo // Hangup Button val hb = ImageButton(this) hb.id = View.generateViewId() hb.setImageResource(R.drawable.hangup) hb.setBackgroundResource(0) prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) prm.marginEnd = 15 prm.bottomMargin = 15 hb.layoutParams = prm hb.setOnClickListener { if (!Utils.isCameraAvailable(this)) Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE) hangupButton.performClick() } videoLayout.addView(hb) hb.visibility = View.INVISIBLE // by ritoseo // 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, " Handling intent '$action'") 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") return } /* Added by ritoseo */ "network available" -> { val tv = findViewById(R.id.textViewHeaderIp) tv.setText(intent.getStringExtra("address")!!) Utils.renderVfdString(tv.text.toString()) } "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() } } /********************/ "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) } } "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 //println("onKeyDown : ${keyCode}") //println("onKeyDown2 : ${event?.scanCode}") val stream = if (am.mode == AudioManager.MODE_RINGTONE) AudioManager.STREAM_RING else AudioManager.STREAM_MUSIC when (keyCode) { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> { am.adjustStreamVolume(stream, if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) AudioManager.ADJUST_LOWER else AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI) Log.d(TAG, "Adjusted volume $keyCode of stream $stream to ${am.getStreamVolume(stream)}") return true } KeyEvent.KEYCODE_BACK -> { //callVideoButton.requestFocus() return true } 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 } BTN_MISC -> { return true } } when (event?.scanCode) { SCANCODE_RED -> { if(!isCallExist()) { startSelfMoitoringCamera() } } SCANCODE_GREEN -> { if(!isCallExist()) { stopSelfMoitoringCamera() } } SCANCODE_OPTION -> { // val dialog = findViewById(R.id.dialogLayout) // if(dialog.isVisible) { // dialog.visibility = View.INVISIBLE // } else { // dialog.visibility = View.VISIBLE // findViewById(R.id.dialogButtonImg1).requestFocus() // } // val dialog = findViewById(R.id.settingLayout) // if (dialog.isVisible) { // dialog.visibility = View.INVISIBLE // } else { // dialog.bringToFront() // dialog.visibility = View.VISIBLE // findViewById(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(R.id.settingLayoutInCall) if (dialog.isVisible) { dialog.visibility = View.INVISIBLE println("settingLayoutInCall 비활성화") } else { dialog.bringToFront() dialog.visibility = View.VISIBLE findViewById(R.id.dialogButtonLayoutInCall).requestFocus() println("settingLayoutInCall 활성화") } } else { val dialog = findViewById(R.id.settingLayout) if (dialog.isVisible) { dialog.visibility = View.INVISIBLE } else { dialog.bringToFront() dialog.visibility = View.VISIBLE findViewById(R.id.dialogButtonLayout).requestFocus() } } // val dialog = findViewById(R.id.dialogBase) // val customButton = LayoutInflater.from(this).inflate(R.layout.image_button, null) // dialog.addView(customButton) return true } } return super.onKeyDown(keyCode, event) } private fun handleServiceEvent(event: String, params: ArrayList) { fun handleNextEvent(logMessage: String? = null) { if (logMessage != null) Log.w(TAG, logMessage) if (BaresipService.serviceEvents.isNotEmpty()) { val first = BaresipService.serviceEvents.removeAt(0) handleServiceEvent(first.event, first.params) } } if (taskId == -1) { handleNextEvent("Omit service event '$event' for task -1") return } if (event == "started") { val uriString = params[0] as String Log.d(TAG, "Handling service event 'started' with URI '$uriString'") if (!this::uaAdapter.isInitialized) { // Android has restarted baresip when permission has been denied in app settings recreate() return } uaAdapter.notifyDataSetChanged() if (uriString != "") { callAction(uriString.toUri(), "dial") } else { if ((aorSpinner.selectedItemPosition == -1) && (BaresipService.uas.size > 0)) { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } } if (Preferences(applicationContext).displayTheme != AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.setDefaultNightMode(Preferences(applicationContext).displayTheme) delegate.applyDayNight() } updateDisplay() BaresipService.supportedCameras = Utils.supportedCameras(applicationContext).isNotEmpty() 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}") } 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 } "message", "message show", "message reply" -> { val i = Intent(applicationContext, ChatActivity::class.java) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val b = Bundle() b.putString("aor", aor) b.putString("peer", params[1] as String) b.putBoolean("focus", ev[0] == "message reply") i.putExtras(b) chatRequests.launch(i) } "mwi notify" -> { val lines = ev[1].split("\n") for (line in lines) { if (line.startsWith("Voice-Message:")) { val counts = (line.split(" ")[1]).split("/") acc.vmNew = counts[0].toInt() acc.vmOld = counts[1].toInt() break } } if (aor == aorSpinner.tag) { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) else voicemailButton.setImageResource(R.drawable.voicemail) } } else -> Log.e(TAG, "Unknown event '${ev[0]}'") } handleNextEvent() } private fun redirect(event: String, ua: UserAgent, redirectUri: String) { if (ua.account.aor != aorSpinner.tag) spinToAor(ua.account.aor) callUri.setText(redirectUri) if (event == "call redirect") callButton.performClick() else callVideoButton.performClick() } private fun reStart() { Log.d(TAG, "Trigger restart") val pm = applicationContext.packageManager val intent = pm.getLaunchIntentForPackage(this.packageName) this.startActivity(intent) exitProcess(0) } override fun onPrepareOptionsMenu(menu: Menu): Boolean { if(Utils.propertyGet("sys.rito.debug") == "1") { if (Build.VERSION.SDK_INT >= 29) menu.findItem(R.id.logcat).setVisible(true) } return super.onPrepareOptionsMenu(menu) } override fun onCreateOptionsMenu(menu: Menu): Boolean { if(Utils.propertyGet("sys.rito.debug") == "1") { menuInflater.inflate(R.menu.main_menu, menu) } // main layout 툴바 상단 아이콘 숨김 - by ritoseo /* menuInflater.inflate(R.menu.rec_icon, menu) recIcon = menu.findItem(R.id.recIcon) if (BaresipService.isRecOn) recIcon!!.setIcon(R.drawable.rec_on) else recIcon!!.setIcon(R.drawable.rec_off) menuInflater.inflate(R.menu.mic_icon, menu) micIcon = menu.findItem(R.id.micIcon) if (BaresipService.isMicMuted) micIcon!!.setIcon(R.drawable.mic_off) else micIcon!!.setIcon(R.drawable.mic_on) menuInflater.inflate(R.menu.speaker_icon, menu) speakerIcon = menu.findItem(R.id.speakerIcon) if (Utils.isSpeakerPhoneOn(am)) speakerIcon!!.setIcon(R.drawable.speaker_on) else speakerIcon!!.setIcon(R.drawable.speaker_off) */ return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.recIcon -> { if (Call.call("connected") == null) { BaresipService.isRecOn = !BaresipService.isRecOn if (BaresipService.isRecOn) { item.setIcon(R.drawable.rec_on) Api.module_load("sndfile") } else { item.setIcon(R.drawable.rec_off) Api.module_unload("sndfile") } } else { Toast.makeText( applicationContext, R.string.rec_in_call, Toast.LENGTH_SHORT ).show() } } R.id.micIcon -> { if (Call.call("connected") != null) { BaresipService.isMicMuted = !BaresipService.isMicMuted if (BaresipService.isMicMuted) { item.setIcon(R.drawable.mic_off) Api.calls_mute(true) } else { item.setIcon(R.drawable.mic_on) Api.calls_mute(false) } } } R.id.speakerIcon -> { if (Build.VERSION.SDK_INT >= 31) Log.d(TAG, "Toggling speakerphone when dev/mode is " + "${am.communicationDevice!!.type}/${am.mode}") Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am) setSpeakerButtonAndIcon() } R.id.config -> { configRequest.launch(Intent(this, ConfigActivity::class.java)) } R.id.accounts -> { val i = Intent(this, AccountsActivity::class.java) val b = Bundle() b.putString("aor", aorSpinner.tag.toString()) i.putExtras(b) accountsRequest.launch(i) } R.id.backup -> { when { Build.VERSION.SDK_INT >= 29 -> pickupFileFromDownloads("backup") ContextCompat.checkSelfPermission( this, WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED -> { Log.d(TAG, "Write External Storage permission granted") val path = Utils.downloadsPath("baresip+.bs") downloadsOutputUri = File(path).toUri() askPassword(getString(R.string.encrypt_password)) } ActivityCompat.shouldShowRequestPermissionRationale( this, WRITE_EXTERNAL_STORAGE) -> { defaultLayout.showSnackBar( binding.root, getString(R.string.no_backup), Snackbar.LENGTH_INDEFINITE, getString(R.string.ok) ) { requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } } else -> { requestPermissionLauncher.launch(WRITE_EXTERNAL_STORAGE) } } } R.id.restore -> { when { Build.VERSION.SDK_INT >= 29 -> pickupFileFromDownloads("restore") ContextCompat.checkSelfPermission( this, READ_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED -> { Log.d(TAG, "Read External Storage permission granted") val path = Utils.downloadsPath("baresip+.bs") downloadsInputUri = File(path).toUri() askPassword(getString(R.string.decrypt_password)) } ActivityCompat.shouldShowRequestPermissionRationale( this, READ_EXTERNAL_STORAGE) -> { defaultLayout.showSnackBar( binding.root, getString(R.string.no_restore), Snackbar.LENGTH_INDEFINITE, getString(R.string.ok) ) { requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } } else -> { requestPermissionLauncher.launch(READ_EXTERNAL_STORAGE) } } } R.id.logcat -> { if (Build.VERSION.SDK_INT >= 29) pickupFileFromDownloads("logcat") } R.id.about -> { startActivity(Intent(this, AboutActivity::class.java)) } R.id.restart, R.id.quit -> { quitRestart(item.itemId == R.id.restart) } } return true } private fun quitRestart(reStart: Boolean) { Log.d(TAG, "quitRestart Restart = $reStart") window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) if (BaresipService.isServiceRunning) { restart = reStart baresipService.action = "Stop" startService(baresipService) } else { finishAndRemoveTask() if (reStart) reStart() else exitProcess(0) } } private fun makeTransfer(ua: UserAgent) { val layout = LayoutInflater.from(this) .inflate(R.layout.call_transfer_dialog, findViewById(android.R.id.content), false) val blind: CheckBox = layout.findViewById(R.id.blind) val attended: CheckBox = layout.findViewById(R.id.attended) val transferUri: AutoCompleteTextView = layout.findViewById(R.id.transferUri) transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, Contact.contactNames())) transferUri.threshold = 2 transferUri.requestFocus() val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) with(builder) { setView(layout) setPositiveButton(R.string.transfer) { dialog, _ -> imm.hideSoftInputFromWindow(transferUri.windowToken, 0) dialog.dismiss() var uriText = transferUri.text.toString().trim() if (uriText.isNotEmpty()) { val uris = Contact.contactUris(uriText) if (uris.size > 1) { val destinationBuilder = MaterialAlertDialogBuilder( this@MainActivity, R.style.AlertDialogTheme ) with(destinationBuilder) { setTitle(R.string.choose_destination_uri) setItems(uris.toTypedArray()) { _, which -> uriText = uris[which] transfer( ua, if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, attended.isChecked ) } setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } show() } } else { if (uris.size == 1) uriText = uris[0] } transfer(ua, if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, attended.isChecked) } } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(transferUri.windowToken, 0) dialog.cancel() } } val alertDialog = builder.create() val call = ua.currentCall() ?: return val blindOrAttended: RelativeLayout = layout.findViewById(R.id.blindOrAttended) if (call.replaces()) { blind.setOnClickListener { if (blind.isChecked) { attended.isChecked = false alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).text = getString(R.string.transfer) } } attended.setOnClickListener { if (attended.isChecked) { blind.isChecked = false alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).text = getString(R.string.call) } } blindOrAttended.visibility = View.VISIBLE } else { blindOrAttended.visibility = View.GONE } // This needs to be done after dialog has been created and before it is shown alertDialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) alertDialog.show() } @RequiresApi(29) private fun pickupFileFromDownloads(action: String) { when (action) { "backup" -> { backupRequest.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/octet-stream" putExtra(Intent.EXTRA_TITLE, "baresip+_" + SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())) putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } "restore" -> { restoreRequest.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/octet-stream" putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } "logcat" -> { logcatRequest.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/plain" putExtra(Intent.EXTRA_TITLE, "baresip_logcat_" + SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())) putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI) }) } } } private fun transfer(ua: UserAgent, uriText: String, attended: Boolean) { val uri = if (Utils.isTelUri(uriText)) Utils.telToSip(uriText, ua.account) else Utils.uriComplete(uriText, ua.account.aor) if (!Utils.checkUri(uri)) { Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) } else { val call = ua.currentCall() if (call != null) { if (attended) { if (call.hold()) { call.referTo = uri call(ua, uri, "voice", call) } } else { if (!call.transfer(uri)) { Utils.alertView(this@MainActivity, getString(R.string.notice), String.format(getString(R.string.transfer_failed))) } } showCall(ua) } } } private fun askPassword(title: String, ua: UserAgent? = null) { val layout = LayoutInflater.from(this) .inflate(R.layout.password_dialog, findViewById(android.R.id.content), false) val titleView: TextView = layout.findViewById(R.id.title) titleView.text = title if (ua != null) { val messageView: TextView = layout.findViewById(R.id.message) val message = getString(R.string.account) + " " + Utils.plainAor(activityAor) messageView.text = message } val input: EditText = layout.findViewById(R.id.password) input.requestFocus() with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setView(layout) setPositiveButton(android.R.string.ok) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.dismiss() var password = input.text.toString().trim() if (!Account.checkAuthPass(password)) { Toast.makeText( applicationContext, String.format(getString(R.string.invalid_authentication_password), password), Toast.LENGTH_SHORT ).show() password = "" } when (title) { getString(R.string.encrypt_password) -> if (password != "") backup(password) getString(R.string.decrypt_password) -> if (password != "") restore(password) else -> if (password == "") { askPassword(title, ua!!) } else { Api.account_set_auth_pass(ua!!.account.accp, password) ua.account.authPass = Api.account_auth_pass(ua.account.accp) BaresipService.aorPasswords[ua.account.aor] = ua.account.authPass if (ua.account.regint == 0) Api.ua_unregister(ua.uap) else Api.ua_register(ua.uap) } } } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.cancel() } val dialog = this.create() dialog.setCancelable(false) dialog.setCanceledOnTouchOutside(false) dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.show() } } private fun getPassword(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") != "")) { return Utils.paramValue(params, "auth_pass").trim('"') } } return "" } private fun askPasswords(accounts: MutableList) { if (accounts.isNotEmpty()) { val account = accounts.removeAt(0) val params = account.substringAfter(">") if ((Utils.paramValue(params, "auth_user") != "") && (Utils.paramValue(params, "auth_pass") == "")) { val aor = account.substringAfter("<").substringBefore(">") val layout = LayoutInflater.from(this) .inflate(R.layout.password_dialog, findViewById(android.R.id.content), false) val titleView: TextView = layout.findViewById(R.id.title) titleView.text = getString(R.string.authentication_password) val messageView: TextView = layout.findViewById(R.id.message) val message = getString(R.string.account) + " " + Utils.plainAor(aor) messageView.text = message val input: EditText = layout.findViewById(R.id.password) input.requestFocus() with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setView(layout) setPositiveButton(android.R.string.ok) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.dismiss() val password = input.text.toString().trim() if (!Account.checkAuthPass(password)) { Toast.makeText( applicationContext, String.format(getString(R.string.invalid_authentication_password), password), Toast.LENGTH_SHORT ).show() accounts.add(0, account) } else { BaresipService.aorPasswords[aor] = password } askPasswords(accounts) } setNeutralButton(android.R.string.cancel) { dialog, _ -> imm.hideSoftInputFromWindow(input.windowToken, 0) dialog.cancel() BaresipService.aorPasswords[aor] = NO_AUTH_PASS askPasswords(accounts) } val dialog = this.create() dialog.setCancelable(false) dialog.setCanceledOnTouchOutside(false) dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.show() } } else { askPasswords(accounts) } } else { requestPermissionsLauncher.launch(permissions) } } private fun startBaresip() { if (!BaresipService.isStartReceived) { baresipService.action = "Start" startService(baresipService) if (atStartup) moveTaskToBack(true) } } private fun backup(password: String) { val files = arrayListOf("accounts", "config", "contacts", "call_history", "messages", "gzrtp.zid", "cert.pem", "ca_cert", "ca_certs.crt") File(BaresipService.filesPath).walk().forEach { if (it.name.endsWith(".png")) files.add(it.name) } val zipFile = getString(R.string.app_name_plus) + ".zip" val zipFilePath = BaresipService.filesPath + "/$zipFile" if (!Utils.zip(files, zipFile)) { Log.w(TAG, "Failed to write zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } val content = Utils.getFileContents(zipFilePath) if (content == null) { Log.w(TAG, "Failed to read zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } if (!Utils.encryptToUri(applicationContext, downloadsOutputUri!!, content, password)) { Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.backup_failed), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) return } Utils.alertView(this, getString(R.string.info), String.format(getString(R.string.backed_up), Utils.fileNameOfUri(applicationContext, downloadsOutputUri!!))) Utils.deleteFile(File(zipFilePath)) } private fun restore(password: String) { val zipFile = getString(R.string.app_name_plus) + ".zip" val zipFilePath = BaresipService.filesPath + "/$zipFile" val zipData = Utils.decryptFromUri(applicationContext, downloadsInputUri!!, password) if (zipData == null) { Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_failed), Utils.fileNameOfUri(applicationContext, downloadsInputUri!!))) return } if (!Utils.putFileContents(zipFilePath, zipData)) { Log.w(TAG, "Failed to write zip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_failed), Utils.fileNameOfUri(applicationContext, downloadsInputUri!!))) return } if (!Utils.unZip(zipFilePath)) { Log.w(TAG, "Failed to unzip file '$zipFile'") Utils.alertView(this, getString(R.string.error), String.format(getString(R.string.restore_unzip_failed), "baresip+", "47.0.0")) return } Utils.deleteFile(File(zipFilePath)) File("${BaresipService.filesPath}/recordings").walk().forEach { if (it.name.startsWith("dump")) Utils.deleteFile(it) } CallHistoryNew.restore() for (h in BaresipService.callHistory) h.recording = arrayOf("", "") CallHistoryNew.save() with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) { setTitle(getString(R.string.info)) setMessage(getString(R.string.restored)) setPositiveButton(getText(R.string.restart)) { dialog, _ -> quitRestart(true) dialog.dismiss() } setNeutralButton(getText(R.string.cancel)) { dialog, _ -> dialog.dismiss() } show() } } private fun spinToAor(aor: String) { for (accountIndex in BaresipService.uas.indices) if (BaresipService.uas[accountIndex].account.aor == aor) { aorSpinner.setSelection(accountIndex) aorSpinner.tag = aor return } if (BaresipService.uas.isNotEmpty()) { aorSpinner.setSelection(0) aorSpinner.tag = BaresipService.uas[0].account.aor } else { aorSpinner.setSelection(-1) aorSpinner.tag = "" } } private fun makeCall(kind: String, lookForContact: Boolean = true) { stopSelfMoitoringCamera() // 모니터링 Self 카메라 멈춤 callUri.setAdapter(null) val ua = BaresipService.uas[aorSpinner.selectedItemPosition] val aor = ua.account.aor if (!Call.inCall()) { var uriText = callUri.text.toString().trim() if (uriText.isNotEmpty()) { if (lookForContact) { val uris = Contact.contactUris(uriText) if (uris.size == 1) uriText = uris[0] else if (uris.size > 1) { val builder = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) with(builder) { setTitle(R.string.choose_destination_uri) setItems(uris.toTypedArray()) { _, which -> callUri.setText(uris[which]) makeCall(kind, false) } setNeutralButton(getString(R.string.cancel)) { _: DialogInterface, _: Int -> } show() } return } } if (Utils.isTelNumber(uriText)) uriText = "tel:$uriText" val uri = if (Utils.isTelUri(uriText)) { if (ua.account.telProvider == "") { Utils.alertView(this, getString(R.string.notice), String.format(getString(R.string.no_telephony_provider), aor)) return } Utils.telToSip(uriText, ua.account) } else { Utils.uriComplete(uriText, aor) } if (!Utils.checkUri(uri)) { Utils.alertView(this, getString(R.string.notice), String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) } else if (!BaresipService.requestAudioFocus(applicationContext)) { Toast.makeText(applicationContext, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show() } else { callUri.isFocusable = false uaAdapter.notifyDataSetChanged() callButton.visibility = View.INVISIBLE callButton.isEnabled = false callVideoButton.visibility = View.INVISIBLE callVideoButton.isEnabled = false switchVideoLayout(true) hangupButton.visibility = View.INVISIBLE hangupButton.isEnabled = false 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 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 callVideoButton.isEnabled = true binding.callBackground.visibility = View.GONE hangupButton.visibility = View.INVISIBLE binding.callBackground.visibility = View.GONE 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 binding.callBackground.visibility = View.VISIBLE hangupButton.visibility = View.INVISIBLE hangupButton.isEnabled = false 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) binding.callBackground.visibility = View.GONE 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) binding.callBackground.visibility = View.VISIBLE hangupButton.visibility = View.INVISIBLE hangupButton.isEnabled = false answerButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE /* if (call.onhold) { holdButton.setImageResource(R.drawable.resume) } else { holdButton.setImageResource(R.drawable.call_hold) } dialpadButton.setImageResource(R.drawable.dialpad_on) dialpadButton.tag = "on" dialpadButton.isEnabled = false infoButton.isEnabled = true callControl.visibility = View.VISIBLE Handler(Looper.getMainLooper()).postDelayed({ onHoldNotice.visibility = if (call.held) View.VISIBLE else View.GONE }, 100) if (call.held) { imm.hideSoftInputFromWindow(dtmf.windowToken, 0) dtmf.isEnabled = false } else { dtmf.isEnabled = true dtmf.requestFocus() if (resources.configuration.orientation == ORIENTATION_PORTRAIT) imm.showSoftInput(dtmf, InputMethodManager.SHOW_IMPLICIT) if (dtmfWatcher != null) dtmf.removeTextChangedListener(dtmfWatcher) dtmfWatcher = call.dtmfWatcher dtmf.addTextChangedListener(dtmfWatcher) } */ } } } } private fun updateIcons(acc: Account) { if (acc.missedCalls) callsButton.setImageResource(R.drawable.calls_missed) else callsButton.setImageResource(R.drawable.calls) if (acc.unreadMessages) messagesButton.setImageResource(R.drawable.messages_unread) else messagesButton.setImageResource(R.drawable.messages) if (acc.vmUri != "") { if (acc.vmNew > 0) voicemailButton.setImageResource(R.drawable.voicemail_new) else voicemailButton.setImageResource(R.drawable.voicemail) voicemailButton.visibility = View.VISIBLE voicemailButtonSpace.visibility = View.VISIBLE } else { voicemailButton.visibility = View.GONE voicemailButtonSpace.visibility =View.GONE } } private fun restoreActivities() { if (BaresipService.activities.isEmpty()) return Log.d(TAG, "Activity stack ${BaresipService.activities}") val activity = BaresipService.activities[0].split(",") BaresipService.activities.removeAt(0) when (activity[0]) { "main" -> { if (!Call.inCall() && (BaresipService.activities.size > 1)) restoreActivities() } "config" -> { configRequest.launch(Intent(this, ConfigActivity::class.java)) } "audio" -> { startActivity(Intent(this, AudioActivity::class.java)) } "accounts" -> { val i = Intent(this, AccountsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) accountsRequest.launch(i) } "account" -> { val i = Intent(this, AccountActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) accountsRequest.launch(i) } "codecs" -> { val i = Intent(this, CodecsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("media", activity[2]) i.putExtras(b) startActivity(i) } "about" -> { startActivity(Intent(this, AboutActivity::class.java)) } "contacts" -> { val i = Intent(this, ContactsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) contactsRequest.launch(i) } "contact" -> { val i = Intent(this, ContactActivity::class.java) val b = Bundle() if (activity[1] == "true") { b.putBoolean("new", true) b.putString("uri", activity[2]) } else { b.putBoolean("new", false) b.putInt("index", activity[2].toInt()) } i.putExtras(b) startActivity(i) } "chats" -> { val i = Intent(this, ChatsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) chatRequests.launch(i) } "chat" -> { val i = Intent(this, ChatActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("peer", activity[2]) b.putBoolean("focus", activity[3] == "true") i.putExtras(b) chatRequests.launch(i) } "calls" -> { val i = Intent(this, CallsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) i.putExtras(b) callsRequest.launch(i) } "call_details" -> { val i = Intent(this, CallDetailsActivity::class.java) val b = Bundle() b.putString("aor", activity[1]) b.putString("peer", activity[2]) b.putInt("position", activity[3].toInt()) i.putExtras(b) callsRequest.launch(i) } } return } private fun saveCallUri() { if (BaresipService.uas.isNotEmpty() && aorSpinner.selectedItemPosition >= 0) { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] if (ua.calls().isEmpty()) ua.account.resumeUri = callUri.text.toString() else ua.account.resumeUri = "" } } private fun startCallTimer(call: Call) { callTimer.stop() callTimer.base = SystemClock.elapsedRealtime() - (call.duration() * 1000L) callTimer.start() } companion object { var accountRequest: ActivityResultLauncher? = null var activityAor = "" lateinit var videoView: VideoView } init { if (!BaresipService.libraryLoaded) { Log.i(TAG, "Loading baresip library") System.loadLibrary("baresip") BaresipService.libraryLoaded = true } } }