ritoseo 74803af21b UI가독성 변경 및 VFD 제어 추가
FakeView incoming call 동작 되도록 처리
2025-09-25 01:46:32 +09:00

4549 lines
188 KiB
Kotlin

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<Event<Long>>
private var recIcon: MenuItem? = null
private var micIcon: MenuItem? = null
private var speakerIcon: MenuItem? = null
private lateinit var speakerButton: ImageButton
private lateinit var swipeRefresh: SwipeRefreshLayout
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private lateinit var accountsRequest: ActivityResultLauncher<Intent>
private lateinit var chatRequests: ActivityResultLauncher<Intent>
private lateinit var configRequest: ActivityResultLauncher<Intent>
private lateinit var backupRequest: ActivityResultLauncher<Intent>
private lateinit var restoreRequest: ActivityResultLauncher<Intent>
private lateinit var logcatRequest: ActivityResultLauncher<Intent>
private lateinit var contactsRequest: ActivityResultLauncher<Intent>
private lateinit var callsRequest: ActivityResultLauncher<Intent>
private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener
private lateinit var permissions: Array<String>
private lateinit var dialogImgBtn1: AppCompatButton
private lateinit var dialogImgBtn2: AppCompatButton
private lateinit var callStartButton: AppCompatButton
private lateinit var callHistoryButton: AppCompatButton
private lateinit var settingButton: AppCompatButton
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<View, View>
lateinit var navUpListAlter : HashMap<View, View>
lateinit var navDownList : HashMap<View, View>
lateinit var navDownListAlter : HashMap<View, View>
lateinit var navUpServerList : HashMap<View, View>
lateinit var navDownServerList : HashMap<View, View>
private var cameraProvider: ProcessCameraProvider? = null
@OptIn(ExperimentalCamera2Interop::class)
private fun cameraSelectorById(targetId: String): CameraSelector {
return CameraSelector.Builder()
.addCameraFilter { cameraInfos: List<CameraInfo> ->
cameraInfos.filter { info ->
Camera2CameraInfo.from(info).cameraId == targetId
}
}
.build()
}
private fun stopSelfMoitoringCamera() {
if(BaresipService.useMonitoringSelfView) {
binding.selfViewLayout.visibility = View.INVISIBLE
// val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
// val cameraProvider = cameraProviderFuture.get()
cameraProvider?.unbindAll()
}
}
@SuppressLint("RestrictedApi")
private fun resetSelfMoitoringCamera() {
if(BaresipService.useMonitoringSelfView) {
for(i in 0..1) {
cameraProvider?.unbindAll()
cameraProvider?.shutdown()
cameraExecutor.shutdown()
}
cameraExecutor = Executors.newSingleThreadExecutor()
}
}
// @SuppressLint("RestrictedApi")
// private fun reinitializeCamera(reason: String) {
// // 1) 모든 UseCase 해제
// cameraProvider?.unbindAll()
//
// // 2) provider 완전 종료
// val oldProvider = cameraProvider
// cameraProvider = null
//
// if (oldProvider != null) {
// val shutdownFuture = oldProvider.shutdown() // ListenableFuture<Void>
// shutdownFuture.addListener({
// // 3) 재시작
// startCameraWithId(targetCameraId)
// }, ContextCompat.getMainExecutor(this))
// } else {
// // 바로 시작
// startCameraWithId(targetCameraId)
// }
// }
private fun startSelfMoitoringCamera() {
if(!BaresipService.useMonitoringSelfView)
return;
binding.selfViewLayout.visibility = View.VISIBLE
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
// ImageAnalysis: 프리뷰 프레임을 가져옴
imageAnalyzer = ImageAnalysis.Builder()
// RGBA_8888로 받으면 YUV→RGB 변환 없이 바로 Bitmap 생성 가능
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
// 최신 프레임만 유지 (버벅임 방지)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { ia ->
ia.setAnalyzer(cameraExecutor, FrameSaver(getOutputDir()))
}
val cameraSelector = cameraSelectorById("120")
try {
cameraProvider?.unbindAll()
val camera = cameraProvider?.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
camera?.cameraInfo?.cameraState?.observe(this) { state ->
when (val type = state.type) {
CameraState.Type.OPEN, CameraState.Type.PENDING_OPEN -> { /* 정상/열리는 중 */ }
CameraState.Type.CLOSING, CameraState.Type.CLOSED -> {
// 닫힘 → 필요 시 재시도 타이머
println("RITO Camera Closing or closed")
}
else -> {}
}
state.error?.let { err ->
when (err.code) {
CameraState.ERROR_CAMERA_IN_USE,
CameraState.ERROR_MAX_CAMERAS_IN_USE,
CameraState.ERROR_CAMERA_DISABLED,
CameraState.ERROR_CAMERA_FATAL_ERROR -> {
// 복구 트리거
println("RITO Camera Error code : ${err.code}")
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}, ContextCompat.getMainExecutor(this))
}
private fun getOutputDir(): File {
// 앱 전용 Pictures 폴더 (권한 불필요)
val dir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File(dir, "preview_frames").apply { if (!exists()) mkdirs() }
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if(!isKeyboardVisible) {
val currentFocusView = currentFocus
if(event.action == KeyEvent.ACTION_UP) {
println("pokaRITO pokachip z. " + event + " focusView : " + currentFocusView)
if(event.keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if(navUpList.contains(currentFocusView)) {
val view = navUpList.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
} else {
if(navUpListAlter.contains(currentFocusView)) {
val view = navUpListAlter.get(currentFocusView)!!
if(view.isVisible) {
view.requestFocus()
return false
}
}
// if(currentFocusView == binding.btnApply) {
// binding.radioDhcp.requestFocus()
// return false
// }
}
} else {
if(currentFocusView == binding.aorSpinner) {
callStartButton.requestFocus()
return false
}
}
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<View>(android.R.id.content)
rootView.viewTreeObserver.addOnGlobalLayoutListener {
val rect = Rect()
rootView.getWindowVisibleDisplayFrame(rect)
val screenHeight = rootView.rootView.height
val keypadHeight = screenHeight - rect.bottom
val visible = keypadHeight > screenHeight * 0.15 // 키보드 올라옴 판단 기준
if (isKeyboardVisible && !visible) {
// 🔻 키보드가 막 내려간 순간!
onKeyboardHidden()
}
isKeyboardVisible = visible
}
}
private fun onKeyboardHidden() {
println( "키보드가 내려갔습니다!")
// val currentFocusView = currentFocus
// if(currentFocusView != null) {
// println( "hideSoftInputFromWindow : " + currentFocusView)
// imm.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
// imm.restartInput(currentFocusView)
// currentFocusView.clearFocus()
// currentFocusView.invalidate()
// currentFocusView.requestFocus()
// }
// Handler(Looper.getMainLooper()).postDelayed({
// findViewById<EditText>(R.id.editIp).requestFocus()
// findViewById<EditText>(R.id.editDns).requestFocus()
// }, 100) // 지연은 상황 따라 조정
}
private fun updateFieldsVisibility() {
val isStatic = binding.radioStatic.isChecked
val visibility = if (isStatic) View.VISIBLE else View.GONE
binding.layoutStaticIp.visibility = visibility
binding.editIp.visibility = visibility
binding.editNetmask.visibility = visibility
binding.editGateway.visibility = visibility
binding.editDns.visibility = visibility
}
fun showCustomToast(context: Context, message: String) {
val inflater = LayoutInflater.from(context)
val layout: View = inflater.inflate(R.layout.custom_toast, null)
val textView: TextView = layout.findViewById(R.id.toast_text)
textView.text = message
val toast = Toast(context)
toast.duration = Toast.LENGTH_LONG
toast.view = layout
toast.show()
}
@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<View, View>()
navUpListAlter = HashMap<View, View>()
navDownList = HashMap<View, View>()
navDownListAlter = HashMap<View, View>()
navUpList.put(binding.editIp, binding.radioStatic)
navUpList.put(binding.editNetmask, binding.editIp)
navUpList.put(binding.editGateway, binding.editNetmask)
navUpList.put(binding.editDns, binding.editGateway)
navUpList.put(binding.btnApply, binding.editDns)
navUpList.put(binding.btnCancel, binding.editDns)
navUpListAlter.put(binding.btnCancel, binding.radioStatic)
navUpListAlter.put(binding.btnApply, binding.radioDhcp)
//navUpList.put(binding.btnApply, binding.radioDhcp)
navDownList.put(binding.radioDhcp, binding.editIp)
navDownList.put(binding.radioStatic, binding.editIp)
navDownListAlter.put(binding.radioStatic, binding.btnApply)
navDownListAlter.put(binding.radioDhcp, binding.btnApply)
navDownList.put(binding.editIp, binding.editNetmask)
navDownList.put(binding.editNetmask, binding.editGateway)
navDownList.put(binding.editGateway, binding.editDns)
navDownList.put(binding.editDns, binding.btnApply)
//navDownList.put(binding.radioDhcp, binding.btnApply)
// setDpadNavigation(binding.editIp, downView = binding.editNetmask)
// setDpadNavigation(binding.editNetmask, upView = binding.editIp, downView = binding.editGateway)
// setDpadNavigation(binding.editGateway, upView = binding.editNetmask, downView = binding.editDns)
// setDpadNavigation(binding.editDns, upView = binding.editGateway, downView = binding.btnApply)
// setDpadNavigation(binding.radioStatic, downView = binding.editIp)
binding.btnApply.setOnClickListener { _ ->
if(binding.radioDhcp.isChecked) {
val params: HashMap<String, String> = HashMap<String, String>()
params.put("IPV4TYPE", "DHCP")
params.put("DEVTYPE", "ETHERNET")
Utils.sendRequestToFactory(applicationContext, "Ipv4", params)
// Toast.makeText(
// applicationContext,
// "DHCP 적용되었습니다.",
// Toast.LENGTH_SHORT
// ).show()
showCustomToast(applicationContext, "DHCP 적용되었습니다.")
binding.networkSettingLayout.visibility = View.INVISIBLE
} else {
if(binding.editIp.text.length == 0 ||
binding.editNetmask.text.length == 0 ||
binding.editGateway.text.length == 0 ||
binding.editDns.text.length == 0) {
showCustomToast(applicationContext, "비어있는 항목이 있습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editIp.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "IP가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editNetmask.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "NETMASK가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editGateway.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "GATEWAY가 유효하지 않습니다.")
return@setOnClickListener
}
if(Utils.IpValidator.detectIpType(binding.editDns.text.toString()) == Utils.IpType.NONE) {
showCustomToast(applicationContext, "DNS가 유효하지 않습니다.")
return@setOnClickListener
}
val params: HashMap<String, String> = HashMap<String, String>()
params.put("IPV4TYPE", "STATIC")
params.put("DEVTYPE", "ETHERNET")
params.put("ADDRESS", binding.editIp.text.toString())
params.put("NETMASK", binding.editNetmask.text.toString())
params.put("GATEWAY", binding.editGateway.text.toString())
params.put("DNS", binding.editDns.text.toString())
Utils.sendRequestToFactory(applicationContext, "Ipv4", params)
// Toast.makeText(
// applicationContext,
// "STATIC 적용되었습니다.",
// Toast.LENGTH_SHORT
// ).show()
showCustomToast(applicationContext, "STATIC 적용되었습니다.")
binding.networkSettingLayout.visibility = View.INVISIBLE
}
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<View, View>()
navDownServerList = HashMap<View, View>()
navUpServerList.put(binding.editDisplayName, binding.btnApplyServer)
navUpServerList.put(binding.editSipId, binding.editDisplayName)
navUpServerList.put(binding.editSipPassword, binding.editSipId)
navUpServerList.put(binding.editSipServer, binding.editSipPassword)
navUpServerList.put(binding.radioTransportUdp, binding.editSipServer)
navUpServerList.put(binding.radioTransportTcp, binding.editSipServer)
navUpServerList.put(binding.radioTransportTls, binding.editSipServer)
navUpServerList.put(binding.radioEncryptNone, binding.radioTransportUdp)
navUpServerList.put(binding.radioEncryptSrtp, binding.radioTransportUdp)
navUpServerList.put(binding.btnApplyServer, binding.radioEncryptNone)
navUpServerList.put(binding.btnCancelServer, binding.radioEncryptNone)
//navUpList.put(binding.btnApply, binding.radioDhcp)
navDownServerList.put(binding.editDisplayName, binding.editSipId)
navDownServerList.put(binding.editSipId, binding.editSipPassword)
navDownServerList.put(binding.editSipPassword, binding.editSipServer)
navDownServerList.put(binding.editSipServer, binding.radioTransportUdp)
navDownServerList.put(binding.radioTransportUdp, binding.radioEncryptNone)
navDownServerList.put(binding.radioTransportTcp, binding.radioEncryptNone)
navDownServerList.put(binding.radioTransportTls, binding.radioEncryptNone)
navDownServerList.put(binding.radioEncryptNone, binding.btnApplyServer)
navDownServerList.put(binding.radioEncryptSrtp, binding.btnApplyServer)
navDownServerList.put(binding.btnApplyServer, binding.editDisplayName)
navDownServerList.put(binding.btnCancelServer, binding.editDisplayName)
updateFieldsVisibility()
val radioGroup = findViewById<RadioGroup>(R.id.radioGroup)
radioGroup.setOnCheckedChangeListener { _, checkedId ->
val selected = findViewById<RadioButton>(checkedId)
selected.requestFocus()
updateFieldsVisibility()
}
serviceEventObserver = Observer {
val event = it.getContentIfNotHandled()
Log.d(TAG, "Observed event $event")
if (event != null && BaresipService.serviceEvents.isNotEmpty()) {
val first = BaresipService.serviceEvents.removeAt(0)
handleServiceEvent(first.event, first.params)
}
}
BaresipService.serviceEvent.observeForever(serviceEventObserver)
screenEventReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context, intent: Intent) {
if (kgm.isKeyguardLocked) {
Log.d(TAG, "Screen on when locked")
this@MainActivity.setShowWhenLocked(Call.inCall())
}
}
}
if(Utils.propertyGet("sys.rito.debug") == "1") {
binding.toolbar.visibility = View.VISIBLE
findViewById<LinearLayout>(R.id.header_bar).visibility = View.GONE
}
this.registerReceiver(screenEventReceiver, IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
})
if (Build.VERSION.SDK_INT >= 31) {
comDevChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device ->
if (device != null) {
Log.d(TAG, "Com device changed to type ${device.type} in mode ${am.mode}")
setSpeakerButtonAndIcon()
}
}
am.addOnCommunicationDeviceChangedListener(mainExecutor, comDevChangedListener)
}
uaAdapter = UaSpinnerAdapter(applicationContext, BaresipService.uas)
aorSpinner.adapter = uaAdapter
aorSpinner.setSelection(-1)
aorSpinner.tag = ""
aorSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
// Have to allow NULL view, since sometimes when onItemSelected is called, view is NULL.
// Haven't found any explanation why this can happen.
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
Log.d(TAG, "aorSpinner selecting $position")
if (position < BaresipService.uas.size) {
val ua = BaresipService.uas[position]
val acc = ua.account
aorSpinner.tag = acc.aor
showCall(ua)
updateIcons(acc)
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
Log.d(TAG, "Nothing selected")
}
}
val registrationObserver = Observer<Long> { uaAdapter.notifyDataSetChanged() }
BaresipService.registrationUpdate.observe(this, registrationObserver)
accountsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
uaAdapter.notifyDataSetChanged()
spinToAor(activityAor)
if (aorSpinner.tag != "")
updateIcons(Account.ofAor(aorSpinner.tag.toString())!!)
if (BaresipService.isServiceRunning) {
baresipService.action = "Update Notification"
startService(baresipService)
}
}
accountRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
spinToAor(activityAor)
val ua = UserAgent.ofAor(activityAor)!!
updateIcons(ua.account)
if (it.resultCode == Activity.RESULT_OK)
if (BaresipService.aorPasswords[activityAor] == NO_AUTH_PASS)
askPassword(getString(R.string.authentication_password), ua)
}
aorSpinner.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if (aorSpinner.selectedItemPosition == -1) {
val i = Intent(this@MainActivity, AccountsActivity::class.java)
val b = Bundle()
b.putString("aor", "")
i.putExtras(b)
accountsRequest.launch(i)
true
} else {
if ((event.x - view.left) < 100) {
val i = Intent(this@MainActivity, AccountActivity::class.java)
val b = Bundle()
b.putString("aor", aorSpinner.tag.toString())
i.putExtras(b)
accountRequest!!.launch(i)
true
} else {
BaresipService.uas[aorSpinner.selectedItemPosition].account.resumeUri =
callUri.text.toString()
false
}
}
} else {
// view.performClick()
false
}
}
aorSpinner.setOnLongClickListener {
if (aorSpinner.selectedItemPosition != -1) {
val ua = UserAgent.ofAor(aorSpinner.tag.toString())
if (ua != null) {
val acc = ua.account
if (Api.account_regint(acc.accp) > 0) {
Api.account_set_regint(acc.accp, 0)
Api.ua_unregister(ua.uap)
} else {
Api.account_set_regint(acc.accp, acc.configuredRegInt)
Api.ua_register(ua.uap)
}
acc.regint = Api.account_regint(acc.accp)
AccountsActivity.saveAccounts()
}
}
true
}
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Contact.contactNames()))
callUri.threshold = 2
callUri.setOnFocusChangeListener { view, b ->
if (b) {
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
}
callUri.setOnClickListener { view ->
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
securityButton.setOnClickListener {
when (securityButton.tag) {
R.drawable.unlocked -> {
Utils.alertView(this, getString(R.string.alert),
getString(R.string.call_not_secure))
}
R.drawable.locked_yellow -> {
Utils.alertView(this, getString(R.string.alert),
getString(R.string.peer_not_verified))
}
R.drawable.locked_green -> {
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle(R.string.info)
setMessage(getString(R.string.call_is_secure))
setPositiveButton(getString(R.string.unverify)) { dialog, _ ->
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val call = ua.currentCall()
if (call != null) {
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) {
Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed")
} else {
securityButton.setImageResource(R.drawable.locked_yellow)
securityButton.tag = R.drawable.locked_yellow
}
}
dialog.dismiss()
}
setNeutralButton(getString(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
}
}
callButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)))
makeCall("voice")
else
Toast.makeText(applicationContext, getString(R.string.no_calls),
Toast.LENGTH_SHORT).show()
}
}
callVideoButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO, CAMERA)))
makeCall("video")
else
Toast.makeText(
applicationContext, getString(R.string.no_video_calls),
Toast.LENGTH_SHORT
).show()
}
}
hangupButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
if (Build.VERSION.SDK_INT < 31) {
if (callRunnable != null) {
callHandler.removeCallbacks(callRunnable!!)
callRunnable = null
BaresipService.abandonAudioFocus(applicationContext)
showCall(ua)
return@setOnClickListener
}
} else {
if (audioModeChangedListener != null) {
am.removeOnModeChangedListener(audioModeChangedListener!!)
audioModeChangedListener = null
BaresipService.abandonAudioFocus(applicationContext)
showCall(ua)
return@setOnClickListener
}
}
val aor = ua.account.aor
val uaCalls = ua.calls()
if (uaCalls.size > 0) {
val call = uaCalls[uaCalls.size - 1]
val callp = call.callp
Log.d(TAG, "AoR $aor hanging up call $callp with ${callUri.text}")
hangupButton.isEnabled = false
Api.ua_hangup(ua.uap, callp, 0, "")
}
}
answerButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val call = ua.currentCall() ?: return@setOnClickListener
Log.d(TAG, "AoR ${ua.account.aor} answering call from ${callUri.text}")
answerButton.isEnabled = false
answerVideoButton.isEnabled = false
rejectButton.isEnabled = false
call.setMediaDirection(Api.SDP_SENDRECV, Api.SDP_INACTIVE)
call.disableVideoStream(true)
val intent = Intent(this@MainActivity, BaresipService::class.java)
intent.action = "Call Answer"
intent.putExtra("uap", ua.uap)
intent.putExtra("callp", call.callp)
startService(intent)
}
answerVideoButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val aor = ua.account.aor
val call = ua.calls("in")[0]
Log.d(TAG, "AoR $aor answering video call ${call.callp} from ${callUri.text}")
answerButton.isEnabled = false
answerVideoButton.isEnabled = false
rejectButton.isEnabled = false
val videoDir = if (Utils.isCameraAvailable(this))
Api.SDP_SENDRECV
else
Api.SDP_RECVONLY
call.setMediaDirection(Api.SDP_SENDRECV, videoDir)
val intent = Intent(this@MainActivity, BaresipService::class.java)
intent.action = "Call Answer"
intent.putExtra("uap", ua.uap)
intent.putExtra("callp", call.callp)
startService(intent)
}
rejectButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val aor = ua.account.aor
val call = ua.currentCall()!!
val callp = call.callp
Log.d(TAG, "AoR $aor rejecting call $callp from ${callUri.text}")
answerButton.isEnabled = false
answerVideoButton.isEnabled = false
rejectButton.isEnabled = false
call.rejected = true
Api.ua_hangup(ua.uap, callp, 486, "Busy Here")
}
holdButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val aor = ua.account.aor
val call = ua.currentCall()!!
if (call.onhold) {
Log.d(TAG, "AoR $aor resuming call ${call.callp} with ${callUri.text}")
call.resume()
call.onhold = false
holdButton.setImageResource(R.drawable.call_hold)
} else {
Log.d(TAG, "AoR $aor holding call ${call.callp} with ${callUri.text}")
call.hold()
call.onhold = true
holdButton.setImageResource(R.drawable.resume)
}
}
transferButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val call = ua.currentCall()
if (call != null ) {
if (call.onHoldCall != null) {
if (!call.executeTransfer())
Utils.alertView(this@MainActivity, getString(R.string.notice),
String.format(getString(R.string.transfer_failed)))
} else {
makeTransfer(ua)
}
}
}
infoButton.setOnClickListener {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val call = ua.currentCall()
if (call != null) {
val stats = call.stats("audio")
if (stats != "") {
val parts = stats.split(",") as ArrayList
if (parts[2] == "0/0") {
parts[2] = "?/?"
parts[3] = "?/?"
parts[4] = "?/?"
}
val codecs = call.audioCodecs()
val duration = call.duration()
val txCodec = codecs.split(',')[0].split("/")
val rxCodec = codecs.split(',')[1].split("/")
Utils.alertView(this, getString(R.string.call_info),
"${String.format(getString(R.string.duration), duration)}\n" +
"${getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/" +
"${rxCodec[0]} ch ${rxCodec[2]}\n" +
"${String.format(getString(R.string.rate), parts[0])}\n" +
"${String.format(getString(R.string.average_rate), parts[1])}\n" +
"${getString(R.string.packets)}: ${parts[2]}\n" +
"${getString(R.string.lost)}: ${parts[3]}\n" +
String.format(getString(R.string.jitter), parts[4]))
} else {
Utils.alertView(this, getString(R.string.call_info),
getString(R.string.call_info_not_available))
}
}
}
dtmf.setOnFocusChangeListener { view, hasFocus ->
if (hasFocus) {
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
}
dtmf.setOnClickListener { view ->
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
voicemailButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val acc = ua.account
if (acc.vmUri != "") {
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val i = Intent(this, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
i.putExtra("action", "call")
i.putExtra("uap", ua.uap)
i.putExtra("peer", acc.vmUri)
startActivity(i)
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle(R.string.voicemail_messages)
setMessage(acc.vmMessages(this@MainActivity))
setPositiveButton(getString(R.string.listen), dialogClickListener)
setNeutralButton(getString(R.string.cancel), dialogClickListener)
show()
}
}
}
}
contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Contact.contactNames()))
}
contactsButton.setOnClickListener {
val i = Intent(this@MainActivity, ContactsActivity::class.java)
val b = Bundle()
if (aorSpinner.selectedItemPosition >= 0)
b.putString("aor", aorSpinner.tag.toString())
else
b.putString("aor", "")
i.putExtras(b)
contactsRequest.launch(i)
}
chatRequests = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
spinToAor(activityAor)
updateIcons(Account.ofAor(activityAor)!!)
}
messagesButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
val i = Intent(this@MainActivity, ChatsActivity::class.java)
val b = Bundle()
b.putString("aor", aorSpinner.tag.toString())
b.putString("peer", resumeUri)
i.putExtras(b)
chatRequests.launch(i)
}
}
callsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
spinToAor(activityAor)
callsButton.setImageResource(R.drawable.calls)
}
callsButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) {
val i = Intent(this@MainActivity, CallsActivity::class.java)
val b = Bundle()
b.putString("aor", aorSpinner.tag.toString())
i.putExtras(b)
callsRequest.launch(i)
}
}
dialpadButton.tag = "off"
dialpadButton.setOnClickListener {
if (dialpadButton.tag == "off") {
callUri.inputType = InputType.TYPE_CLASS_PHONE
dialpadButton.setImageResource(R.drawable.dialpad_on)
dialpadButton.tag = "on"
//Log.d(TAG, "Screen ${Utils.getScreenOrientation(applicationContext)}")
//val path = BaresipService.downloadsPath + "/video.mp4"
//Utils.ffmpegExecute("-video_size hd720 -f android_camera -camera_index 1 -i anything -r 10 -t 5 -y $path")
} else {
callUri.inputType = InputType.TYPE_CLASS_TEXT +
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
dialpadButton.setImageResource(R.drawable.dialpad_off)
dialpadButton.tag = "off"
}
}
videoButton.setOnClickListener {
videoButton.isClickable = false
videoButton.setImageResource(R.drawable.video_pending)
Handler(Looper.getMainLooper()).postDelayed({
val call = Call.call("connected")
if (call != null) {
val dir = call.videoRequest
if (dir != 0) {
call.videoRequest = 0
call.setVideoDirection(dir)
} else {
if (Utils.isCameraAvailable(this))
call.setVideoDirection(Api.SDP_SENDRECV)
else
call.setVideoDirection(Api.SDP_RECVONLY)
}
imm.hideSoftInputFromWindow(dtmf.windowToken, 0)
}
}, 250)
}
configRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if ((it.data != null) && it.data!!.hasExtra("restart")) {
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle(R.string.restart_request)
setMessage(getString(R.string.config_restart))
setPositiveButton(getText(R.string.restart)) { dialog, _ ->
dialog.dismiss()
quitRestart(true)
}
setNeutralButton(getText(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
val displayTheme = Preferences(applicationContext).displayTheme
if (displayTheme != AppCompatDelegate.getDefaultNightMode()) {
AppCompatDelegate.setDefaultNightMode(displayTheme)
delegate.applyDayNight()
}
}
backupRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK)
it.data?.data?.also { uri ->
downloadsOutputUri = uri
askPassword(getString(R.string.encrypt_password))
}
}
restoreRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK)
it.data?.data?.also { uri ->
downloadsInputUri = uri
askPassword(getString(R.string.decrypt_password))
}
}
logcatRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK)
it.data?.data?.also { uri ->
try {
val out = contentResolver.openOutputStream(uri)
val process = Runtime.getRuntime().exec("logcat -d --pid=${Process.myPid()}")
val bufferedReader = process.inputStream.bufferedReader()
bufferedReader.forEachLine { line ->
out!!.write(line.toByteArray())
out.write('\n'.code.toByte().toInt())
}
out!!.close()
} catch (e: Exception) {
Log.e(TAG, "Failed to write logcat to file")
}
}
}
swipeRefresh.setOnTouchListener(object : OnSwipeTouchListener(this@MainActivity) {
override fun onSwipeLeft() {
super.onSwipeLeft()
if (BaresipService.uas.size > 0) {
val curPos = aorSpinner.selectedItemPosition
val newPos = if (curPos == -1)
0
else
(curPos + 1) % BaresipService.uas.size
if (curPos != newPos) {
aorSpinner.setSelection(newPos)
showCall(BaresipService.uas[newPos])
}
}
}
override fun onSwipeRight() {
super.onSwipeRight()
if (BaresipService.uas.size > 0) {
val curPos = aorSpinner.selectedItemPosition
val newPos = when (curPos) {
-1 -> 0
0 -> BaresipService.uas.size - 1
else -> curPos - 1
}
if (curPos != newPos) {
aorSpinner.setSelection(newPos)
showCall(BaresipService.uas[newPos])
}
}
}
})
swipeRefresh.setOnRefreshListener {
if (BaresipService.uas.size > 0) {
if (aorSpinner.selectedItemPosition == -1)
aorSpinner.setSelection(0)
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
if (ua.account.regint > 0)
Api.ua_register(ua.uap)
}
swipeRefresh.isRefreshing = false
}
baresipService = Intent(this@MainActivity, BaresipService::class.java)
atStartup = intent.hasExtra("onStartup")
when (intent?.action) {
ACTION_DIAL, ACTION_CALL, ACTION_VIEW ->
if (BaresipService.isServiceRunning)
callAction(intent.data, if (intent?.action == ACTION_CALL) "call" else "dial")
else
BaresipService.callActionUri = intent.data.toString()
.replace("tel:%2B", "tel:+")
}
permissions = if (BaresipService.supportedCameras) {
if (Build.VERSION.SDK_INT >= 33)
arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT)
else if (Build.VERSION.SDK_INT >= 31)
arrayOf(RECORD_AUDIO, CAMERA, BLUETOOTH_CONNECT)
else
arrayOf(RECORD_AUDIO, CAMERA)
} else {
if (Build.VERSION.SDK_INT >= 33)
arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, BLUETOOTH_CONNECT)
else if (Build.VERSION.SDK_INT >= 31)
arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT)
else
arrayOf(RECORD_AUDIO)
}
requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
val denied = mutableListOf<String>()
val shouldShow = mutableListOf<String>()
it.forEach { permission ->
if (!permission.value) {
denied.add(permission.key)
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
permission.key))
shouldShow.add(permission.key)
}
}
if (denied.contains(POST_NOTIFICATIONS) &&
!shouldShow.contains(POST_NOTIFICATIONS)) {
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle(getString(R.string.notice))
setMessage(getString(R.string.no_notifications))
setPositiveButton(getString(R.string.ok)) { _, _ ->
quitRestart(false)
}
show()
}
} else {
if (shouldShow.isNotEmpty())
Utils.alertView(this, getString(R.string.permissions_rationale),
if (CAMERA in permissions)
getString(R.string.audio_and_video_permissions)
else
getString(R.string.audio_permissions)
) { requestPermissionsLauncher.launch(permissions) }
// 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<TextView>(R.id.textViewHeaderIp)
tv.setText(BaresipService.deviceIpAddress)
}
override fun onPause() {
super.onPause()
Log.d(TAG, "Main onPause")
Utils.addActivity("main")
BaresipService.isMainVisible = false
callTimer.stop()
saveCallUri()
}
override fun onStop() {
super.onStop()
Log.d(TAG, "Main onStop")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "Main onDestroy")
this.unregisterReceiver(screenEventReceiver)
if (Build.VERSION.SDK_INT >= 31)
am.removeOnCommunicationDeviceChangedListener(comDevChangedListener)
BaresipService.serviceEvent.removeObserver(serviceEventObserver)
BaresipService.serviceEvents.clear()
BaresipService.activities.clear()
}
var prevLayoutShow = false
private fun switchVideoLayout(show : Boolean) {
if(show == prevLayoutShow) {
return
}
prevLayoutShow = show
if(BaresipService.connectedDisplayCount < 2)
return
println("switchVideoLayout : ${show}")
if(show) {
// var prm: FrameLayout.LayoutParams =
// FrameLayout.LayoutParams(
// 1920,
// 1080
// )
// prm.leftMargin = 0
// prm.topMargin = 0
// videoView.surfaceSelfView.layoutParams = prm
// try {
// val parentView = videoView.surfaceSelfView.parent
// (parentView as ViewGroup).removeView(videoView.surfaceSelfView)
// } catch(e : java.lang.Exception) {
// }
// presentation?.setContentView(videoView.surfaceSelfView)
videoView.surfaceView.bringToFront()
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<TextView>(R.id.textViewDeviceName)
view.setText(BaresipService.deviceName)
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일(E요일) a h시 m분", Locale.KOREAN)
val viewDate = findViewById<TextView>(R.id.textViewDatetime)
val formattedDate = now.format(formatter)
viewDate.setText(formattedDate)
}
private fun updateDisplay() {
println("Update Display : ${BaresipService.connectedDisplayCount}, prevDisplayCount : ${BaresipService.prevConnectedDisplayCount}")
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<RelativeLayout>(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<RelativeLayout>(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<RelativeLayout>(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<RelativeLayout>(R.id.presentationLayout)
presentationView?.addView(videoView.surfaceSelfView)
presentationView?.addView(videoView.standbyView)
//presentation?.setContentView(videoView.standbyView)
}
presentation?.show()
} else {
var prm2: RelativeLayout.LayoutParams =
RelativeLayout.LayoutParams(
1920,
1080
)
prm2.leftMargin = 1919
prm2.topMargin = 1079
videoView.surfaceSelfView.layoutParams = prm2
videoLayout.addView(videoView.surfaceSelfView)
//videoView.surfaceSelfView.visibility = View.INVISIBLE
}
if(useSecondScreenAsFar) {
videoLayout.addView(videoView.surfaceSelfView)
} else {
videoLayout.addView(videoView.surfaceView)
}
}
// Video Button
val vb = ImageButton(this)
vb.id = View.generateViewId()
vb.setImageResource(R.drawable.video_off)
vb.setBackgroundResource(0)
var prm: RelativeLayout.LayoutParams =
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
prm.marginStart = 15
prm.bottomMargin = 15
vb.layoutParams = prm
vb.setOnClickListener {
Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE)
}
videoLayout.addView(vb)
vb.visibility = View.INVISIBLE // by ritoseo
// Camera Button
val cb = ImageButton(this)
if (!Utils.isCameraAvailable(this))
cb.visibility = View.INVISIBLE
cb.visibility = View.INVISIBLE // by ritoseo
cb.id = View.generateViewId()
cb.setImageResource(R.drawable.camera_front)
cb.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
)
prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
prm.addRule(RelativeLayout.ALIGN_PARENT_TOP)
prm.marginStart = 15
prm.topMargin = 15
cb.layoutParams = prm
cb.setOnClickListener {
val call = Call.call("connected")
if (call != null) {
if (call.setVideoSource(!BaresipService.cameraFront) != 0)
Log.w(TAG, "Failed to set video source")
else
BaresipService.cameraFront = !BaresipService.cameraFront
if (BaresipService.cameraFront)
cb.setImageResource(R.drawable.camera_front)
else
cb.setImageResource(R.drawable.camera_rear)
}
}
videoLayout.addView(cb)
// Snapshot Button
if ((Build.VERSION.SDK_INT >= 29) ||
Utils.checkPermissions(this, arrayOf(WRITE_EXTERNAL_STORAGE))) {
val sb = ImageButton(this)
sb.setImageResource(R.drawable.snapshot)
sb.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
prm.addRule(RelativeLayout.BELOW, cb.id)
prm.marginStart = 15
prm.topMargin= 24
sb.layoutParams = prm
sb.setOnClickListener {
val sdf = SimpleDateFormat("yyyyMMdd_hhmmss", Locale.getDefault())
val fileName = "IMG_" + sdf.format(Date()) + ".png"
val filePath = BaresipService.filesPath + "/" + fileName
if (Api.cmd_exec("snapshot_recv $filePath") != 0)
Log.e(TAG, "Command 'snapshot_recv $filePath' failed")
else
MediaActionSound().play(MediaActionSound.SHUTTER_CLICK)
}
videoLayout.addView(sb)
sb.visibility = View.INVISIBLE // by ritoseo
}
// Lock Button
videoSecurityButton = ImageButton(this)
videoSecurityButton.visibility = View.INVISIBLE
videoSecurityButton.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
prm.addRule(RelativeLayout.ABOVE, vb.id)
prm.marginStart = 15
prm.bottomMargin= 24
videoSecurityButton.layoutParams = prm
videoSecurityButton.setOnClickListener {
when (securityButton.tag) {
R.drawable.unlocked -> {
Utils.alertView(this, getString(R.string.alert),
getString(R.string.call_not_secure))
}
R.drawable.locked_yellow -> {
Utils.alertView(this, getString(R.string.alert),
getString(R.string.peer_not_verified))
}
R.drawable.locked_green -> {
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle(R.string.info)
setMessage(getString(R.string.call_is_secure))
setPositiveButton(getString(R.string.unverify)) { dialog, _ ->
val ua = BaresipService.uas[aorSpinner.selectedItemPosition]
val call = ua.currentCall()
if (call != null) {
if (Api.cmd_exec("zrtp_unverify " + call.zid) != 0) {
Log.e(TAG, "Command 'zrtp_unverify ${call.zid}' failed")
} else {
securityButton.tag = R.drawable.locked_yellow
videoSecurityButton.setImageResource(R.drawable.locked_video_yellow)
}
}
dialog.dismiss()
}
setNeutralButton(getString(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
show()
}
}
}
}
videoLayout.addView(videoSecurityButton)
videoSecurityButton.visibility = View.INVISIBLE // by ritoseo
// Speaker Button
speakerButton = ImageButton(this)
speakerButton.id = View.generateViewId()
speakerButton.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
prm.addRule(RelativeLayout.ALIGN_PARENT_TOP)
prm.marginEnd = 15
prm.topMargin = 15
speakerButton.layoutParams = prm
speakerButton.setOnClickListener {
Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am)
setSpeakerButtonAndIcon()
}
setSpeakerButtonAndIcon()
videoLayout.addView(speakerButton)
speakerButton.visibility = View.INVISIBLE // by ritoseo
// Mic Button
val mb = ImageButton(this)
mb.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
prm.addRule(RelativeLayout.BELOW, speakerButton.id)
prm.marginEnd = 15
prm.topMargin= 24
mb.layoutParams = prm
if (BaresipService.isMicMuted)
mb.setImageResource(R.drawable.mic_off_button)
else
mb.setImageResource(R.drawable.mic_on_button)
mb.setOnClickListener {
BaresipService.isMicMuted = !BaresipService.isMicMuted
if (BaresipService.isMicMuted) {
mb.setImageResource(R.drawable.mic_off_button)
Api.calls_mute(true)
} else {
mb.setImageResource(R.drawable.mic_on_button)
Api.calls_mute(false)
}
}
videoLayout.addView(mb)
mb.visibility = View.INVISIBLE // by ritoseo
// Hangup Button
val hb = ImageButton(this)
hb.id = View.generateViewId()
hb.setImageResource(R.drawable.hangup)
hb.setBackgroundResource(0)
prm = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT)
prm.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
prm.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
prm.marginEnd = 15
prm.bottomMargin = 15
hb.layoutParams = prm
hb.setOnClickListener {
if (!Utils.isCameraAvailable(this))
Call.call("connected")?.setVideoDirection(Api.SDP_INACTIVE)
hangupButton.performClick()
}
videoLayout.addView(hb)
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, "<RITO> 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<TextView>(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<FrameLayout>(R.id.dialogLayout)
// if(dialog.isVisible) {
// dialog.visibility = View.INVISIBLE
// } else {
// dialog.visibility = View.VISIBLE
// findViewById<AppCompatButton>(R.id.dialogButtonImg1).requestFocus()
// }
// val dialog = findViewById<FrameLayout>(R.id.settingLayout)
// if (dialog.isVisible) {
// dialog.visibility = View.INVISIBLE
// } else {
// dialog.bringToFront()
// dialog.visibility = View.VISIBLE
// findViewById<AppCompatButton>(R.id.dialogButtonLayout).requestFocus()
// }
if(binding.layoutSettingLayout.visibility == View.VISIBLE ||
binding.networkSettingLayout.visibility == View.VISIBLE ||
binding.serverSettingLayout.visibility == View.VISIBLE ||
binding.volumeSettingLayout.visibility == View.VISIBLE)
return true
if (Call.inCall()) {
val dialog = findViewById<FrameLayout>(R.id.settingLayoutInCall)
if (dialog.isVisible) {
dialog.visibility = View.INVISIBLE
println("settingLayoutInCall 비활성화")
} else {
dialog.bringToFront()
dialog.visibility = View.VISIBLE
findViewById<AppCompatButton>(R.id.dialogButtonLayoutInCall).requestFocus()
println("settingLayoutInCall 활성화")
}
} else {
val dialog = findViewById<FrameLayout>(R.id.settingLayout)
if (dialog.isVisible) {
dialog.visibility = View.INVISIBLE
} else {
dialog.bringToFront()
dialog.visibility = View.VISIBLE
findViewById<AppCompatButton>(R.id.dialogButtonLayout).requestFocus()
}
}
// val dialog = findViewById<FrameLayout>(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<Any>) {
fun handleNextEvent(logMessage: String? = null) {
if (logMessage != null)
Log.w(TAG, logMessage)
if (BaresipService.serviceEvents.isNotEmpty()) {
val first = BaresipService.serviceEvents.removeAt(0)
handleServiceEvent(first.event, first.params)
}
}
if (taskId == -1) {
handleNextEvent("Omit service event '$event' for task -1")
return
}
if (event == "started") {
val uriString = params[0] as String
Log.d(TAG, "Handling service event 'started' with URI '$uriString'")
if (!this::uaAdapter.isInitialized) {
// Android has restarted baresip when permission has been denied in app settings
recreate()
return
}
uaAdapter.notifyDataSetChanged()
if (uriString != "") {
callAction(uriString.toUri(), "dial")
} else {
if ((aorSpinner.selectedItemPosition == -1) && (BaresipService.uas.size > 0)) {
aorSpinner.setSelection(0)
aorSpinner.tag = BaresipService.uas[0].account.aor
}
}
if (Preferences(applicationContext).displayTheme != AppCompatDelegate.getDefaultNightMode()) {
AppCompatDelegate.setDefaultNightMode(Preferences(applicationContext).displayTheme)
delegate.applyDayNight()
}
updateDisplay()
BaresipService.supportedCameras = Utils.supportedCameras(applicationContext).isNotEmpty()
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>) : 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<String>) {
if (accounts.isNotEmpty()) {
val account = accounts.removeAt(0)
val params = account.substringAfter(">")
if ((Utils.paramValue(params, "auth_user") != "") &&
(Utils.paramValue(params, "auth_pass") == "")) {
val aor = account.substringAfter("<").substringBefore(">")
val layout = LayoutInflater.from(this)
.inflate(R.layout.password_dialog, findViewById(android.R.id.content),
false)
val titleView: TextView = layout.findViewById(R.id.title)
titleView.text = getString(R.string.authentication_password)
val messageView: TextView = layout.findViewById(R.id.message)
val message = getString(R.string.account) + " " + Utils.plainAor(aor)
messageView.text = message
val input: EditText = layout.findViewById(R.id.password)
input.requestFocus()
with (MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setView(layout)
setPositiveButton(android.R.string.ok) { dialog, _ ->
imm.hideSoftInputFromWindow(input.windowToken, 0)
dialog.dismiss()
val password = input.text.toString().trim()
if (!Account.checkAuthPass(password)) {
Toast.makeText(
applicationContext,
String.format(getString(R.string.invalid_authentication_password),
password),
Toast.LENGTH_SHORT
).show()
accounts.add(0, account)
} else {
BaresipService.aorPasswords[aor] = password
}
askPasswords(accounts)
}
setNeutralButton(android.R.string.cancel) { dialog, _ ->
imm.hideSoftInputFromWindow(input.windowToken, 0)
dialog.cancel()
BaresipService.aorPasswords[aor] = NO_AUTH_PASS
askPasswords(accounts)
}
val dialog = this.create()
dialog.setCancelable(false)
dialog.setCanceledOnTouchOutside(false)
dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show()
}
} else {
askPasswords(accounts)
}
} else {
requestPermissionsLauncher.launch(permissions)
}
}
private fun startBaresip() {
if (!BaresipService.isStartReceived) {
baresipService.action = "Start"
startService(baresipService)
if (atStartup)
moveTaskToBack(true)
}
}
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<Intent>? = null
var activityAor = ""
lateinit var videoView: VideoView
}
init {
if (!BaresipService.libraryLoaded) {
Log.i(TAG, "Loading baresip library")
System.loadLibrary("baresip")
BaresipService.libraryLoaded = true
}
}
}