마이크 볼륨 설정 추가

This commit is contained in:
ritoseo 2025-08-26 23:36:34 +09:00
parent 4ca03bc1c9
commit 4130bd4e4b
32 changed files with 1580 additions and 48 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId = 'kr.co.rito.ritosip' applicationId = 'kr.co.rito.ritosip'
minSdkVersion 29 minSdkVersion 29
targetSdkVersion 35 targetSdkVersion 35
versionCode = 100 versionCode = 104
versionName = '1.0.0' versionName = '1.0.4'
externalNativeBuild { externalNativeBuild {
cmake { cmake {
cFlags '-DHAVE_INTTYPES_H -lstdc++' cFlags '-DHAVE_INTTYPES_H -lstdc++'
@ -45,6 +45,7 @@ android {
// minifyEnabled false // minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
signingConfig signingConfigs.debug
} }
} }
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->

View File

@ -23,7 +23,7 @@
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" android:maxSdkVersion="32"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />

View File

@ -29,6 +29,7 @@ module vp8.so
module vp9.so module vp9.so
module av1.so module av1.so
module snapshot.so module snapshot.so
module fakevideo.so
module stun.so module stun.so
module turn.so module turn.so
module ice.so module ice.so
@ -37,11 +38,13 @@ module dtls_srtp.so
module gzrtp.so module gzrtp.so
module uuid.so module uuid.so
module vumeter.so module vumeter.so
module webrtc_aecm.so
module_app account.so module_app account.so
module_app debug_cmd.so module_app debug_cmd.so
module_app mwi.so module_app mwi.so
avcodec_h264enc h264_mediacodec avcodec_h264enc h264_mediacodec
video_fps 30 video_fps 30
mic_volume 20
evdev_device /dev/input/event0 evdev_device /dev/input/event0
opus_samplerate 48000 opus_samplerate 48000
opus_stereo no opus_stereo no

View File

@ -1785,6 +1785,18 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_plus_Api_call_1set_1video_1source
return err; return err;
} }
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_plus_Api_call_1set_1video_1fake(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
int err;
re_thread_enter();
err = video_set_source(call_video((struct call *)call), "fakevideo", NULL);
re_thread_leave();
return err;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_plus_Api_call_1destroy( JNIEXPORT void JNICALL Java_com_tutpro_baresip_plus_Api_call_1destroy(
JNIEnv *env, jobject obj, jlong call) JNIEnv *env, jobject obj, jlong call)
{ {
@ -1808,6 +1820,36 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_plus_Api_cmd_1exec(
return res; return res;
} }
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_plus_Api_set_1audio_1source(JNIEnv *env, jobject obj)
{
(void)obj;
struct list *aucodecl = baresip_ausrcl();
struct le *le;
char codec_buf[256];
char *start = &(codec_buf[0]);
unsigned int left = sizeof codec_buf;
int len;
for (le = list_head(aucodecl); le != NULL; le = le->next) {
const struct ausrc *ac = le->data;
if (start == &(codec_buf[0]))
len = re_snprintf(start, left, "<%s>", ac->name);
else
len = re_snprintf(start, left, ",<%s>", ac->name);
if (len == -1) {
LOGE("failed to print codec to buffer\n");
codec_buf[0] = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
start = start + len;
left = left - len;
}
*start = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_plus_Api_audio_1codecs(JNIEnv *env, jobject obj) JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_plus_Api_audio_1codecs(JNIEnv *env, jobject obj)
{ {
(void)obj; (void)obj;

View File

@ -98,6 +98,7 @@ object Api {
external fun call_state(callp: Long): Int external fun call_state(callp: Long): Int
external fun call_has_video(callp: Long): Boolean external fun call_has_video(callp: Long): Boolean
external fun call_set_video_source(callp: Long, front: Boolean): Int external fun call_set_video_source(callp: Long, front: Boolean): Int
external fun call_set_video_fake(callp: Long): Int // added by ritoseo
external fun call_set_video_direction(callp: Long, dir: Int) external fun call_set_video_direction(callp: Long, dir: Int)
external fun call_set_video_mute(callp: Long, mute: Boolean) // added by ritoseo external fun call_set_video_mute(callp: Long, mute: Boolean) // added by ritoseo
external fun call_set_media_direction(callp: Long, adir: Int, vdir: Int) external fun call_set_media_direction(callp: Long, adir: Int, vdir: Int)
@ -113,6 +114,7 @@ object Api {
external fun message_send(uap: Long, peer_uri: String, message: String, time: String): Int external fun message_send(uap: Long, peer_uri: String, message: String, time: String): Int
external fun set_audio_source(): String
external fun audio_codecs(): String external fun audio_codecs(): String
external fun video_codecs(): String external fun video_codecs(): String

View File

@ -35,14 +35,12 @@ import android.telecom.TelecomManager
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.util.JsonReader
import android.util.Size import android.util.Size
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -63,11 +61,8 @@ import org.json.JSONObject
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.nio.ByteBuffer
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.time.Clock
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@ -269,7 +264,6 @@ class BaresipService: Service() {
} }
} }
sipReqeustReceiver = object : BroadcastReceiver() { sipReqeustReceiver = object : BroadcastReceiver() {
fun sendContactList() { fun sendContactList() {
val resArr : JSONArray = JSONArray() val resArr : JSONArray = JSONArray()
@ -570,11 +564,17 @@ class BaresipService: Service() {
if(param != null) { if(param != null) {
val json = JSONObject(param) val json = JSONObject(param)
val device_name = json.getString("device_name") val device_name = json.getString("device_name")
val mic_volume = json.getString("mic_volume")
val display_type = json.getString("display_type") val display_type = json.getString("display_type")
Config.replaceVariable("device_name", device_name) Config.replaceVariable("device_name", device_name)
BaresipService.deviceName = device_name BaresipService.deviceName = device_name
val mic_volume_int = mic_volume.toInt()
if(mic_volume_int >= 0 && mic_volume_int <= 24) {
Utils.setMicVolumeByMix(mic_volume_int)
}
Config.replaceVariable("far_view_display_id", display_type) Config.replaceVariable("far_view_display_id", display_type)
BaresipService.farViewDisplayId = display_type.toInt() BaresipService.farViewDisplayId = display_type.toInt()
Config.save() Config.save()
@ -759,14 +759,28 @@ class BaresipService: Service() {
} }
val scope = CoroutineScope(Dispatchers.Default) val scope = CoroutineScope(Dispatchers.Default)
var captureCount = 0 var captureCount : Long = 0
var running = true var running = true
val job = scope.launch { val job = scope.launch {
println("camera capture manager is running...") println("camera capture manager is running...")
Utils.deleteFiles("/mnt/obb", "jpg") Utils.deleteFiles("/mnt/obb", "jpg")
var updateDate = LocalDateTime.now() var updateDate = LocalDateTime.now()
var cameraState = -1
while (running) { while (running) {
// var devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS)
// for (device in devices) {
// var typeName = "모름 ${device.type}"
// if(device.type == AudioDeviceInfo.TYPE_USB_HEADSET) {
// typeName = "USB헤드셋(${device.id})"
// } else if(device.type == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
// typeName = "내장마이크(${device.id})"
// }
// println("[입력장치] ${typeName} : ${device.productName}")
// }
updateAudioSourceDevice(applicationContext)
if(File("/mnt/obb/camera_near.rgb565.done").exists() && !File("/mnt/obb/camera_near.jpg").exists()) { if(File("/mnt/obb/camera_near.rgb565.done").exists() && !File("/mnt/obb/camera_near.jpg").exists()) {
try { try {
val nearBuf = Utils.readFileToByteBuffer("/mnt/obb/camera_near.rgb565") val nearBuf = Utils.readFileToByteBuffer("/mnt/obb/camera_near.rgb565")
@ -970,25 +984,88 @@ class BaresipService: Service() {
val camera1 = Utils.checkCameraConnection(0) val camera1 = Utils.checkCameraConnection(0)
val camera2 = Utils.checkCameraConnection(1) val camera2 = Utils.checkCameraConnection(1)
val prevCamera1 = Utils.propertyGet("sys.ritosip.camera1.status")
Utils.propertySet("sys.ritosip.camera1.status", camera1) Utils.propertySet("sys.ritosip.camera1.status", camera1)
//call.setVideoSource(!BaresipService.cameraFront) //call.setVideoSource(!BaresipService.cameraFront)
val prevCamera2 = Utils.propertyGet("sys.ritosip.camera2.status") val prevCamera2 = Utils.propertyGet("sys.ritosip.camera2.status")
Utils.propertySet("sys.ritosip.camera2.status", camera2) Utils.propertySet("sys.ritosip.camera2.status", camera2)
if(camera2 != prevCamera2) { if(camera1 != "plugin" && camera2 != "plugin" && cameraState != 0) {
if(uas.size > 0) { if(uas.size > 0) {
val ua = uas[0] val ua = uas[0]
val call = ua.currentCall() val call = ua.currentCall()
if(call != null) { if(call != null) {
if(camera2 == "plugin") { call.setVideoFake()
BaresipService.cameraFront = false; // Contents Input cameraState = 0
call.setVideoSource(false)
} else {
BaresipService.cameraFront = true; // Camera Input
call.setVideoSource(true)
}
} }
} }
} else if(camera1 == "plugin" && camera2 != "plugin" && (cameraState == 0 || cameraState == 2)) {
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
if(call != null) {
Log.d(TAG, "RITO camera1 detect!")
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall android.hardware.camera.provider@2.4-service android.hardware.camera.provider@2.4-external-service cameraserver android.hardware.tv.input@1.0-service")
if(cameraState == 0) {
val process = Runtime.getRuntime()
.exec("ritosysc shell-order=killall cameraserver")
process.waitFor()
process.destroy()
delay(1500) // 1초마다 실행
}
BaresipService.cameraFront = true;
call.setVideoSource(true)
cameraState = 1
//delay(3000) // 1초마다 실행
}
}
} else if(camera2 == "plugin" && cameraState != 2) {
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
if(call != null) {
Log.d(TAG, "RITO camera2 detect!")
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall android.hardware.camera.provider@2.4-service android.hardware.camera.provider@2.4-external-service cameraserver android.hardware.tv.input@1.0-service")
// val process = Runtime.getRuntime().exec("ritosysc shell-order=killall cameraserver")
// process.waitFor()
// process.destroy()
// delay(1500) // 1초마다 실행
BaresipService.cameraFront = false;
call.setVideoSource(false)
cameraState = 2
//delay(3000) // 1초마다 실행
}
}
}
// else if(camera2 != prevCamera2) {
// if(uas.size > 0) {
// val ua = uas[0]
// val call = ua.currentCall()
//
// if(call != null) {
// cameraState = 2
// if(camera2 == "plugin") {
// BaresipService.cameraFront = false; // Contents Input
// call.setVideoSource(false)
// } else {
// BaresipService.cameraFront = true; // Camera Input
// call.setVideoSource(true)
// }
// }
// }
// }
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
if(call == null) {
cameraState = -1
}
} else {
cameraState = -1
} }
if(BaresipService.cameraFront) { if(BaresipService.cameraFront) {
@ -1000,15 +1077,17 @@ class BaresipService: Service() {
val usbMicExist = Utils.checkUsbMicrophone() val usbMicExist = Utils.checkUsbMicrophone()
if(usbMicExist) { if(usbMicExist) {
Utils.propertySet("sys.ritosip.usb.microphone", "connected") Utils.propertySet("sys.ritosip.usb.microphone", "connected")
if(audioInputDevice == "usb") { // if(audioInputDevice == "usb") {
Utils.propertySet("sys.rito.audio.input.device", "usb") // Utils.propertySet("sys.rito.audio.input.device", "usb")
} else { // } else {
Utils.propertySet("sys.rito.audio.input.device", "codec") // Utils.propertySet("sys.rito.audio.input.device", "codec")
} // }
} else { } else {
Utils.propertySet("sys.ritosip.usb.microphone", "disconnected") Utils.propertySet("sys.ritosip.usb.microphone", "disconnected")
} }
Utils.setAudioSourceDevice(audioInputDevice)
delay(100) // 1초마다 실행 delay(100) // 1초마다 실행
val now = LocalDateTime.now() val now = LocalDateTime.now()
@ -1051,7 +1130,7 @@ class BaresipService: Service() {
if (farFilePath.length > 0) if (farFilePath.length > 0)
Utils.propertySet("sys.ritosip.far_screen_file", farFilePath) Utils.propertySet("sys.ritosip.far_screen_file", farFilePath)
} else { } else {
Utils.propertySet("sys.ritosip.near_screen_file", "") //Utils.propertySet("sys.ritosip.near_screen_file", "")
Utils.propertySet("sys.ritosip.far_screen_file", "") Utils.propertySet("sys.ritosip.far_screen_file", "")
} }
} }
@ -1068,6 +1147,54 @@ class BaresipService: Service() {
Utils.propertySet("sys.ritosip.display_split_mode", BaresipService.displaySplitMode.toString()) Utils.propertySet("sys.ritosip.display_split_mode", BaresipService.displaySplitMode.toString())
Utils.propertySet("sys.ritosip.sip.total_duration", BaresipService.totalDurationSeconds.toString()) Utils.propertySet("sys.ritosip.sip.total_duration", BaresipService.totalDurationSeconds.toString())
// Call 중이 아닌 경우 Local Camera 화면 캡쳐
val camera1 = Utils.checkCameraConnection(0)
if(cameraState == -1) {
if(camera1 == "plugin") {
//Utils.runShellOrderSuper("killall -9 v4l2-ctl")
Utils.runShellOrderSuper("timeout 2s v4l2-ctl --device=/dev/video20 --stream-mmap=3 --stream-to=/mnt/obb/camera.rgb888 --stream-count=1 && chmod 666 /mnt/obb/camera.rgb888")
try {
val nearBuf = Utils.readFileToByteBuffer("/mnt/obb/camera.rgb888")
var bufSize = nearBuf.capacity()
var width : Int = 1280
var height : Int = 720
if(bufSize == 1280 * 720 * 3) {
width = 1280
height = 720
} else if(bufSize == 1920 * 1080 * 3) {
width = 1920
height = 1080
}
if(bufSize > 0) {
var nearFilePath = "/mnt/obb/near_$captureCount.jpg"
Utils.saveRGB888AsJPG(
applicationContext,
nearBuf,
width,
height,
nearFilePath
)
val destFile = File(nearFilePath)
destFile.setReadable(true, false)
Utils.propertySet("sys.ritosip.near_screen_file", nearFilePath)
}
} catch(e : Exception) {
//e.printStackTrace()
}
} else {
var nearFilePath = "/mnt/obb/near_$captureCount.jpg"
Utils.saveDrawableAsJpg(applicationContext, R.drawable.nocamera, nearFilePath)
val destFile = File(nearFilePath)
destFile.setReadable(true, false)
Utils.propertySet("sys.ritosip.near_screen_file", nearFilePath)
}
}
captureCount++ captureCount++
delay(100) delay(100)
@ -1161,7 +1288,7 @@ class BaresipService: Service() {
CallHistoryNew.save() CallHistoryNew.save()
} }
val value = Utils.readNumberFromFile("/sdcard/Documents/sip_total_duration") val value = Utils.readNumberFromFile("/sdcard/Documents/sip_total_duration.txt")
if(value != null) { if(value != null) {
BaresipService.totalDurationSeconds = value BaresipService.totalDurationSeconds = value
} }
@ -1225,6 +1352,8 @@ class BaresipService: Service() {
applyTransportConfiguration() applyTransportConfiguration()
Utils.setMicVolumeByMix(BaresipService.micVolume)
} }
"Start Content Observer" -> { "Start Content Observer" -> {
@ -1826,7 +1955,9 @@ class BaresipService: Service() {
/* Added by ritoseo */ /* Added by ritoseo */
val duration = call.duration() val duration = call.duration()
BaresipService.totalDurationSeconds += duration BaresipService.totalDurationSeconds += duration
Utils.saveNumberToFile("/sdcard/Documents/sip_total_duration", BaresipService.totalDurationSeconds) Utils.saveNumberToFile("/sdcard/Documents/sip_total_duration.txt", BaresipService.totalDurationSeconds)
//Utils.saveNumberToFile(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "sip_total_duration").absolutePath, BaresipService.totalDurationSeconds)
/* ---------------- */ /* ---------------- */
if (!Utils.isVisible()) { if (!Utils.isVisible()) {
@ -2267,11 +2398,15 @@ class BaresipService: Service() {
} }
private fun setCallVolume() { private fun setCallVolume() {
println("Call volume setting!")
callVolume = 0
if (callVolume != 0) if (callVolume != 0)
for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) { for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) {
origVolume[streamType] = am.getStreamVolume(streamType) origVolume[streamType] = am.getStreamVolume(streamType)
val maxVolume = am.getStreamMaxVolume(streamType) val maxVolume = am.getStreamMaxVolume(streamType)
am.setStreamVolume(streamType, (callVolume * 0.1 * maxVolume).roundToInt(), 0) am.setStreamVolume(streamType, (callVolume * 0.1 * maxVolume).roundToInt(), 0)
println("Orig/new/max $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
Log.d(TAG, "Orig/new/max $streamType volume is " + Log.d(TAG, "Orig/new/max $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume") "${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
} }
@ -2548,6 +2683,26 @@ class BaresipService: Service() {
var nearViewDisplayId = 2 var nearViewDisplayId = 2
var farViewLayout : Rect = Rect(0, 0, 1920, 1080) var farViewLayout : Rect = Rect(0, 0, 1920, 1080)
var nearViewLayout : Rect = Rect(0, 0, 1920, 1080) var nearViewLayout : Rect = Rect(0, 0, 1920, 1080)
var audioDeviceUsbId = -1
var audioDeviceCodecId = -1
var micVolume = 20
fun updateAudioSourceDevice(ctx: Context) {
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
var devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS)
var usbId = -1
var codecId = -1
for (device in devices) {
if(device.type == AudioDeviceInfo.TYPE_USB_HEADSET) {
usbId = device.id
} else if(device.type == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
codecId = device.id
}
}
audioDeviceUsbId = usbId
audioDeviceCodecId = codecId
}
fun requestAudioFocus(ctx: Context): Boolean { fun requestAudioFocus(ctx: Context): Boolean {
Log.d(TAG, "Requesting audio focus") Log.d(TAG, "Requesting audio focus")

View File

@ -46,6 +46,10 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
return Api.call_set_video_source(callp, front) return Api.call_set_video_source(callp, front)
} }
fun setVideoFake(): Int {
return Api.call_set_video_fake(callp)
}
fun hold(): Boolean { fun hold(): Boolean {
return Api.call_hold(callp, true) == 0 return Api.call_hold(callp, true) == 0
} }

View File

@ -211,6 +211,14 @@ object Config {
*/ */
/* Added by ritoseo */ /* Added by ritoseo */
val micVolume = previousVariable("mic_volume")
if (micVolume != "") {
config = "${config}mic_volume $micVolume\n"
BaresipService.micVolume = micVolume.toInt()
} else {
config = "${config}mic_volume ${BaresipService.micVolume}\n"
}
for (size in defaultSizes) for (size in defaultSizes)
videoSizes.add(size.toString()) videoSizes.add(size.toString())
/********************/ /********************/
@ -225,12 +233,15 @@ object Config {
Size(videoSize.substringBefore("x").toInt(), Size(videoSize.substringBefore("x").toInt(),
videoSize.substringAfter("x").toInt()) videoSize.substringAfter("x").toInt())
} }
BaresipService.videoSize = Size(1280, 720)
//BaresipService.videoSize = Size(640, 480) //BaresipService.videoSize = Size(640, 480)
config = "${config}video_size " + config = "${config}video_size " +
"${BaresipService.videoSize.width}x${BaresipService.videoSize.height}\n" "${BaresipService.videoSize.width}x${BaresipService.videoSize.height}\n"
/* Added by ritoseo */ /* Added by ritoseo */
config = "${config}video_bitrate 60000000\n" config = "${config}video_bitrate 100000000\n"
config = "${config}rtp_video_tos 184\n" config = "${config}rtp_video_tos 184\n"
config = "${config}rtp_bandwidth 512-8192\n" config = "${config}rtp_bandwidth 512-8192\n"
/********************/ /********************/

View File

@ -11,6 +11,7 @@ import android.content.*
import android.content.Intent.ACTION_CALL import android.content.Intent.ACTION_CALL
import android.content.Intent.ACTION_DIAL import android.content.Intent.ACTION_DIAL
import android.content.Intent.ACTION_VIEW import android.content.Intent.ACTION_VIEW
import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect import android.graphics.Rect
@ -19,10 +20,8 @@ import android.media.AudioManager
import android.media.MediaActionSound import android.media.MediaActionSound
import android.net.Uri import android.net.Uri
import android.os.* import android.os.*
import android.os.StrictMode.VmPolicy
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.MediaStore.Audio.Radio
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.util.TypedValue import android.util.TypedValue
@ -48,14 +47,18 @@ import androidx.lifecycle.Observer
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar 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.Utils.showSnackBar
import com.tutpro.baresip.plus.databinding.ActivityMainBinding import com.tutpro.baresip.plus.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import kotlin.collections.HashMap
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -138,6 +141,11 @@ class MainActivity : AppCompatActivity() {
private lateinit var callStartButton: AppCompatButton private lateinit var callStartButton: AppCompatButton
private lateinit var callHistoryButton: AppCompatButton private lateinit var callHistoryButton: AppCompatButton
private lateinit var settingButton: 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 var callHandler: Handler = Handler(Looper.getMainLooper()) private var callHandler: Handler = Handler(Looper.getMainLooper())
private var callRunnable: Runnable? = null private var callRunnable: Runnable? = null
@ -191,6 +199,9 @@ class MainActivity : AppCompatActivity() {
lateinit var navUpList : HashMap<View, View> lateinit var navUpList : HashMap<View, View>
lateinit var navDownList : HashMap<View, View> lateinit var navDownList : HashMap<View, View>
lateinit var navUpServerList : HashMap<View, View>
lateinit var navDownServerList : HashMap<View, View>
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if(!isKeyboardVisible) { if(!isKeyboardVisible) {
val currentFocusView = currentFocus val currentFocusView = currentFocus
@ -214,6 +225,24 @@ class MainActivity : AppCompatActivity() {
return false 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) { else if(event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if(navDownList.contains(currentFocusView)) { if(navDownList.contains(currentFocusView)) {
@ -223,12 +252,22 @@ class MainActivity : AppCompatActivity() {
return false 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) { } else if(event.keyCode == KeyEvent.KEYCODE_ENTER) {
if(currentFocusView == binding.editIp || if(currentFocusView == binding.editIp ||
currentFocusView == binding.editGateway || currentFocusView == binding.editGateway ||
currentFocusView == binding.editNetmask || currentFocusView == binding.editNetmask ||
currentFocusView == binding.editDns) { currentFocusView == binding.editDns ||
currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword
) {
if(imm.isActive) { if(imm.isActive) {
imm.showSoftInput(currentFocusView, InputMethodManager.SHOW_IMPLICIT) imm.showSoftInput(currentFocusView, InputMethodManager.SHOW_IMPLICIT)
imm.restartInput(currentFocusView) imm.restartInput(currentFocusView)
@ -242,6 +281,9 @@ class MainActivity : AppCompatActivity() {
if(navUpList.contains(currentFocusView) || navDownList.contains(currentFocusView)) { if(navUpList.contains(currentFocusView) || navDownList.contains(currentFocusView)) {
found = true found = true
} }
if(navUpServerList.contains(currentFocusView) || navDownServerList.contains(currentFocusView)) {
found = true
}
if(found) { if(found) {
return true return true
@ -253,6 +295,12 @@ class MainActivity : AppCompatActivity() {
currentFocusView == binding.editDns) { currentFocusView == binding.editDns) {
return true return true
} }
if(currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword) {
return true
}
} }
} }
} else { } else {
@ -389,6 +437,8 @@ class MainActivity : AppCompatActivity() {
setupKeyboardVisibilityListener() setupKeyboardVisibilityListener()
Utils.propertySet("sys.ritosip.version", getAppVersion(this))
// Must be done after view has been created // Must be done after view has been created
this.setShowWhenLocked(true) this.setShowWhenLocked(true)
this.setTurnScreenOn( true) this.setTurnScreenOn( true)
@ -453,10 +503,213 @@ class MainActivity : AppCompatActivity() {
// binding.baseButtonLayout.visibility = View.INVISIBLE // binding.baseButtonLayout.visibility = View.INVISIBLE
// binding.mainActivityLayout.visibility = View.INVISIBLE // binding.mainActivityLayout.visibility = View.INVISIBLE
// binding.defaultLayout.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.networkSettingLayout.visibility = View.VISIBLE
binding.radioDhcp.requestFocus() 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.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.seekbarMicVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
// progress: 현재 값
// fromUser: 사용자가 직접 움직였는지 여부
val currentValue = progress
println("현재 값: $currentValue")
binding.textMicVolume.setText(currentValue.toString())
Utils.setMicVolumeByMix(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() BaresipService.supportedCameras = Utils.supportedCameras(applicationContext).isNotEmpty()
@ -530,12 +783,87 @@ class MainActivity : AppCompatActivity() {
showCustomToast(applicationContext, "STATIC 적용되었습니다.") showCustomToast(applicationContext, "STATIC 적용되었습니다.")
binding.networkSettingLayout.visibility = View.INVISIBLE binding.networkSettingLayout.visibility = View.INVISIBLE
} }
binding.dialogButtonNetwork.requestFocus()
} }
binding.btnCancel.setOnClickListener { _ -> binding.btnCancel.setOnClickListener { _ ->
binding.networkSettingLayout.visibility = View.INVISIBLE 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()
}
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() updateFieldsVisibility()
val radioGroup = findViewById<RadioGroup>(R.id.radioGroup) val radioGroup = findViewById<RadioGroup>(R.id.radioGroup)
@ -1179,11 +1507,12 @@ class MainActivity : AppCompatActivity() {
else else
getString(R.string.audio_permissions) getString(R.string.audio_permissions)
) { requestPermissionsLauncher.launch(permissions) } ) { requestPermissionsLauncher.launch(permissions) }
else // else
startBaresip() // startBaresip()
} }
} }
startBaresip()
addVideoLayoutViews() addVideoLayoutViews()
if (!BaresipService.isServiceRunning) { if (!BaresipService.isServiceRunning) {
@ -1192,7 +1521,7 @@ class MainActivity : AppCompatActivity() {
Utils.getFileContents(filesDir.absolutePath + "/accounts")!!, Utils.getFileContents(filesDir.absolutePath + "/accounts")!!,
Charsets.UTF_8 Charsets.UTF_8
).lines().toMutableList() ).lines().toMutableList()
askPasswords(accounts) //askPasswords(accounts)
} else { } else {
// Baresip is started for the first time // Baresip is started for the first time
requestPermissionsLauncher.launch(permissions) requestPermissionsLauncher.launch(permissions)
@ -1201,6 +1530,14 @@ class MainActivity : AppCompatActivity() {
} // OnCreate } // 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() { override fun onStart() {
super.onStart() super.onStart()
Log.d(TAG, "Main onStart") Log.d(TAG, "Main onStart")
@ -2055,6 +2392,7 @@ class MainActivity : AppCompatActivity() {
hangupButton.performClick() hangupButton.performClick()
} }
videoLayout.addView(hb) videoLayout.addView(hb)
hb.visibility = View.INVISIBLE // by ritoseo
// Info Button // Info Button
val ib = ImageButton(this) val ib = ImageButton(this)
@ -2354,13 +2692,50 @@ class MainActivity : AppCompatActivity() {
} }
when (event?.scanCode) { when (event?.scanCode) {
SCANCODE_OPTION -> { SCANCODE_OPTION -> {
val dialog = findViewById<FrameLayout>(R.id.dialogLayout) // val dialog = findViewById<FrameLayout>(R.id.dialogLayout)
if(dialog.isVisible) { // if(dialog.isVisible) {
dialog.visibility = View.INVISIBLE // 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 { } else {
dialog.visibility = View.VISIBLE val dialog = findViewById<FrameLayout>(R.id.settingLayout)
findViewById<AppCompatButton>(R.id.dialogButtonImg1).requestFocus() 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 dialog = findViewById<FrameLayout>(R.id.dialogBase)
// val customButton = LayoutInflater.from(this).inflate(R.layout.image_button, null) // val customButton = LayoutInflater.from(this).inflate(R.layout.image_button, null)
// dialog.addView(customButton) // dialog.addView(customButton)
@ -3097,6 +3472,19 @@ class MainActivity : AppCompatActivity() {
} }
} }
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>) { private fun askPasswords(accounts: MutableList<String>) {
if (accounts.isNotEmpty()) { if (accounts.isNotEmpty()) {
val account = accounts.removeAt(0) val account = accounts.removeAt(0)
@ -3315,8 +3703,8 @@ class MainActivity : AppCompatActivity() {
callVideoButton.visibility = View.INVISIBLE callVideoButton.visibility = View.INVISIBLE
callVideoButton.isEnabled = false callVideoButton.isEnabled = false
switchVideoLayout(true) switchVideoLayout(true)
hangupButton.visibility = View.VISIBLE hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = true hangupButton.isEnabled = false
if (Build.VERSION.SDK_INT < 31) { if (Build.VERSION.SDK_INT < 31) {
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
am.mode = AudioManager.MODE_IN_COMMUNICATION am.mode = AudioManager.MODE_IN_COMMUNICATION
@ -3470,7 +3858,10 @@ class MainActivity : AppCompatActivity() {
//callVideoButton.visibility = View.VISIBLE //callVideoButton.visibility = View.VISIBLE
callVideoButton.visibility = View.INVISIBLE // modified by ritoseo callVideoButton.visibility = View.INVISIBLE // modified by ritoseo
callVideoButton.isEnabled = true callVideoButton.isEnabled = true
binding.callBackground.visibility = View.GONE
hangupButton.visibility = View.INVISIBLE hangupButton.visibility = View.INVISIBLE
binding.callBackground.visibility = View.GONE
answerButton.visibility = View.INVISIBLE answerButton.visibility = View.INVISIBLE
answerVideoButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE
rejectButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE
@ -3501,8 +3892,10 @@ class MainActivity : AppCompatActivity() {
securityButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE
diverter.visibility = View.GONE diverter.visibility = View.GONE
callButton.visibility = View.INVISIBLE callButton.visibility = View.INVISIBLE
hangupButton.visibility = View.VISIBLE
hangupButton.isEnabled = true binding.callBackground.visibility = View.VISIBLE
hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
answerButton.visibility = View.INVISIBLE answerButton.visibility = View.INVISIBLE
answerVideoButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE
rejectButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE
@ -3528,6 +3921,8 @@ class MainActivity : AppCompatActivity() {
callButton.visibility = View.INVISIBLE callButton.visibility = View.INVISIBLE
callVideoButton.visibility = View.INVISIBLE callVideoButton.visibility = View.INVISIBLE
switchVideoLayout(true) switchVideoLayout(true)
binding.callBackground.visibility = View.GONE
hangupButton.visibility = View.INVISIBLE hangupButton.visibility = View.INVISIBLE
answerButton.visibility = View.VISIBLE answerButton.visibility = View.VISIBLE
answerButton.isEnabled = true answerButton.isEnabled = true
@ -3603,8 +3998,9 @@ class MainActivity : AppCompatActivity() {
switchVideoLayout(true) switchVideoLayout(true)
hangupButton.visibility = View.VISIBLE binding.callBackground.visibility = View.VISIBLE
hangupButton.isEnabled = true hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
answerButton.visibility = View.INVISIBLE answerButton.visibility = View.INVISIBLE
answerVideoButton.visibility = View.INVISIBLE answerVideoButton.visibility = View.INVISIBLE
rejectButton.visibility = View.INVISIBLE rejectButton.visibility = View.INVISIBLE

View File

@ -14,6 +14,8 @@ import android.graphics.Bitmap.createScaledBitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.hardware.camera2.CameraAccessException import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
@ -49,6 +51,9 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.* import java.io.*
import java.lang.reflect.Method import java.lang.reflect.Method
import java.net.Inet4Address import java.net.Inet4Address
@ -1175,6 +1180,62 @@ object Utils {
} }
} }
fun rgb888ToBitmap(rgbData: ByteArray, width: Int, height: Int): Bitmap? {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val pixels = IntArray(width * height)
for (i in pixels.indices) {
val b = rgbData[i * 3].toInt() and 0xFF
val g = rgbData[i * 3 + 1].toInt() and 0xFF
val r = rgbData[i * 3 + 2].toInt() and 0xFF
pixels[i] = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
}
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
}
fun saveRGB888AsJPG(context: Context, rgb565Data: ByteBuffer, width: Int, height: Int, fileName: String) {
// RGB565 포맷의 빈 Bitmap 생성
val bitmap = rgb888ToBitmap(rgb565Data.array(), width, height)
if(bitmap == null)
return
try {
// 저장할 파일 경로 설정
val file = File(fileName)
try {
FileOutputStream(file).use { fos ->
// Bitmap을 JPG 형식으로 저장 (품질 90%)
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos)
fos.flush()
}
} catch (e: IOException) {
e.printStackTrace()
}
} catch(e: java.lang.Exception) {
}
}
fun saveDrawableAsJpg(context: Context, drawableResId: Int, filename: String): Boolean {
val drawable: Drawable? = ContextCompat.getDrawable(context, drawableResId)
if (drawable == null || drawable !is BitmapDrawable) {
return false
}
val bitmap: Bitmap = drawable.bitmap
val file = File(filename)
return try {
FileOutputStream(file).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
}
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
fun copyFile(sourcePath: String, destinationPath: String): Boolean { fun copyFile(sourcePath: String, destinationPath: String): Boolean {
return try { return try {
FileInputStream(sourcePath).use { input -> FileInputStream(sourcePath).use { input ->
@ -1259,7 +1320,7 @@ object Utils {
} }
} }
fun setAudioSourceDevice(dev : String) { fun setAudioSourceDeviceV1(dev : String) {
propertySet("sys.rito.audio.input.device", dev) propertySet("sys.rito.audio.input.device", dev)
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver") //val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver")
val process = Runtime.getRuntime().exec("ritosysc shell-order=killall android.hardware.audio.service") val process = Runtime.getRuntime().exec("ritosysc shell-order=killall android.hardware.audio.service")
@ -1267,6 +1328,22 @@ object Utils {
process.destroy() process.destroy()
} }
fun setAudioSourceDevice(dev : String) {
propertySet("sys.ritosip.audio.input.device", dev)
if(dev == "usb") {
if(BaresipService.audioDeviceUsbId == -1) {
propertySet("sys.ritosip.audio.source", BaresipService.audioDeviceCodecId.toString())
propertySet("sys.ritosip.audio.source.volume", "0")
} else {
propertySet("sys.ritosip.audio.source", BaresipService.audioDeviceUsbId.toString())
propertySet("sys.ritosip.audio.source.volume", "100")
}
} else if(dev == "codec") {
propertySet("sys.ritosip.audio.source", BaresipService.audioDeviceCodecId.toString())
propertySet("sys.ritosip.audio.source.volume", "100")
}
}
fun checkNetworkIpType() { fun checkNetworkIpType() {
val process = Runtime.getRuntime().exec("ritosysc shell-order=rm /mnt/obb/ip_static;grep STATIC /data/misc/ethernet/ipconfig.txt && touch /mnt/obb/ip_static") val process = Runtime.getRuntime().exec("ritosysc shell-order=rm /mnt/obb/ip_static;grep STATIC /data/misc/ethernet/ipconfig.txt && touch /mnt/obb/ip_static")
process.waitFor() process.waitFor()
@ -1279,6 +1356,28 @@ object Utils {
} }
} }
fun runShellOrder(order : String) {
val process = Runtime.getRuntime().exec(order)
process.waitFor()
process.destroy()
}
fun setMicVolumeByMix(volume : Int) {
CoroutineScope(Dispatchers.Default).launch {
Utils.runShellOrderSuper("tinymix -D 3 \"Mic Capture Volume\" ${volume}")
BaresipService.micVolume = volume
Config.replaceVariable("mic_volume", volume.toString())
Config.save()
propertySet("sys.ritosip.mic.volume", volume.toString())
}
}
fun runShellOrderSuper(order : String) {
val process = Runtime.getRuntime().exec("ritosysc shell-order=" + order)
process.waitFor()
process.destroy()
}
fun save(dev : String) { fun save(dev : String) {
propertySet("sys.rito.audio.input.device", dev) propertySet("sys.rito.audio.input.device", dev)
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver") //val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver")
@ -1315,7 +1414,17 @@ object Utils {
val file = File("/d/hdmirx/status") // 파일 경로 설정 val file = File("/d/hdmirx/status") // 파일 경로 설정
file.bufferedReader().use { reader -> file.bufferedReader().use { reader ->
val line = reader.readLine() // 첫 번째 줄 읽기 val line = reader.readLine() // 첫 번째 줄 읽기
return line.split(":")[1].trim() val plugStatus = line.split(":")[1].trim()
if(plugStatus == "plugin") {
val line2 = reader.readLine() // 두 번째 줄 읽기
val lockStatus = line2.split(":")[1].trim().startsWith("Lock")
if (lockStatus) {
return "plugin"
}
}
return "plugout"
} }
} else if(idx == 1) { } else if(idx == 1) {
val file = File("/sys/module/lt6911uxc/parameters/status") // 파일 경로 설정 val file = File("/sys/module/lt6911uxc/parameters/status") // 파일 경로 설정
@ -1334,13 +1443,21 @@ object Utils {
fun saveNumberToFile(path: String, number: Int) { fun saveNumberToFile(path: String, number: Int) {
val file = File(path) val file = File(path)
try { try {
//println("파일 존재 여부: ${file.exists()}, 디렉토리 여부: ${file.isDirectory}, 파일 여부: ${file.isFile}")
if(!file.exists()) {
file.createNewFile()
}
file.writeText(number.toString()) file.writeText(number.toString())
Log.d("FileSave", "숫자 저장 완료: $number") Log.d("FileSave", "숫자 저장 완료: $number")
RandomAccessFile(path, "rw").use { raf -> RandomAccessFile(path, "rw").use { raf ->
raf.fd.sync() // eMMC에 확실히 기록 raf.fd.sync() // eMMC에 확실히 기록
raf.close() raf.close()
} }
file.setWritable(true, false)
} catch (e: IOException) { } catch (e: IOException) {
Log.e("FileSave", "파일 저장 실패 : " + e.toString()) Log.e("FileSave", "파일 저장 실패 : " + e.toString())
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape android:shape="rectangle">
<stroke android:width="2dp" android:color="#FFFFFF" />
<solid android:color="#0098FF" /> <!-- 포커스 배경 (주황색 강조) -->
<corners android:radius="16dp" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<stroke android:width="2dp" android:color="#FFFFFF" />
<solid android:color="#26A7FF" /> <!-- 포커스 배경 (주황색 강조) -->
<corners android:radius="16dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<stroke android:width="2dp" android:color="#88FFFFFF" />
<solid android:color="#c2c2c2" /> <!-- 포커스 배경 (회색) -->
<corners android:radius="16dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,5 @@
<vector android:height="128dp" android:width="128dp"
android:viewportHeight="24" android:viewportWidth="24"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/colorTrafficRed" android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -19,6 +19,15 @@
app:srcCompat="@drawable/osvc_splash" /> app:srcCompat="@drawable/osvc_splash" />
</FrameLayout> </FrameLayout>
<FrameLayout
android:id="@+id/callBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBlack"
android:visibility="gone">
</FrameLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/defaultLayout" android:id="@+id/defaultLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -662,6 +671,228 @@
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
<FrameLayout
android:id="@+id/settingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="60px"
android:layout_marginVertical="150px"
android:background="@drawable/custom_border"
android:visibility="invisible">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonLayout"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="90px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/layout"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:nextFocusLeft="@id/dialogButtonBackward"
android:nextFocusRight="@id/dialogButtonServer"
android:nextFocusUp="@id/dialogButtonLayout"
android:nextFocusDown="@id/dialogButtonLayout"
android:text="레이아웃설정"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonServer"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="432px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/server"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="서버설정"
android:nextFocusLeft="@id/dialogButtonLayout"
android:nextFocusRight="@id/dialogButtonNetwork"
android:nextFocusUp="@id/dialogButtonServer"
android:nextFocusDown="@id/dialogButtonServer"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonNetwork"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="774px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/network"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="네트워크설정"
android:nextFocusLeft="@id/dialogButtonServer"
android:nextFocusRight="@id/dialogButtonVolume"
android:nextFocusUp="@id/dialogButtonNetwork"
android:nextFocusDown="@id/dialogButtonNetwork"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonVolume"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="1116px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/volume"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="볼륨설정"
android:nextFocusLeft="@id/dialogButtonNetwork"
android:nextFocusRight="@id/dialogButtonBackward"
android:nextFocusUp="@id/dialogButtonVolume"
android:nextFocusDown="@id/dialogButtonVolume"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonBackward"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="1458px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/backward"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="돌아가기"
android:nextFocusLeft="@id/dialogButtonVolume"
android:nextFocusRight="@id/dialogButtonLayout"
android:nextFocusUp="@id/dialogButtonBackward"
android:nextFocusDown="@id/dialogButtonBackward"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
</FrameLayout>
<FrameLayout
android:id="@+id/settingLayoutInCall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="150px"
android:layout_marginVertical="150px"
android:background="@drawable/custom_border"
android:visibility="invisible">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonLayoutInCall"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="100px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/layout"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:nextFocusLeft="@id/dialogButtonBackwardInCall"
android:nextFocusRight="@id/dialogButtonHangUp"
android:nextFocusUp="@id/dialogButtonLayoutInCall"
android:nextFocusDown="@id/dialogButtonLayoutInCall"
android:text="레이아웃설정"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonHangUp"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="480px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/hangup_big"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="통화종료"
android:nextFocusLeft="@id/dialogButtonLayoutInCall"
android:nextFocusRight="@id/dialogButtonVolumeInCall"
android:nextFocusUp="@id/dialogButtonHangUp"
android:nextFocusDown="@id/dialogButtonHangUp"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonVolumeInCall"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="860px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/volume"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="볼륨설정"
android:nextFocusLeft="@id/dialogButtonHangUp"
android:nextFocusRight="@id/dialogButtonBackwardInCall"
android:nextFocusUp="@id/dialogButtonVolumeInCall"
android:nextFocusDown="@id/dialogButtonVolumeInCall"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialogButtonBackwardInCall"
android:layout_width="250px"
android:layout_height="wrap_content"
android:layout_marginLeft="1240px"
android:layout_marginTop="250px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/backward"
android:drawablePadding="25px"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="16dp"
android:text="돌아가기"
android:nextFocusLeft="@id/dialogButtonVolumeInCall"
android:nextFocusRight="@id/dialogButtonLayoutInCall"
android:nextFocusUp="@id/dialogButtonBackwardInCall"
android:nextFocusDown="@id/dialogButtonBackwardInCall"
android:textColor="@color/colorWhite"
android:textSize="30sp"
android:textStyle="bold" />
</FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/networkSettingLayout" android:id="@+id/networkSettingLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -761,6 +992,8 @@
android:layout_margin="16dp" android:layout_margin="16dp"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:nextFocusLeft="@id/btnApply"
android:nextFocusRight="@id/btnCancel"
android:text="적용하기" android:text="적용하기"
android:textSize="24sp" /> android:textSize="24sp" />
@ -771,10 +1004,549 @@
android:layout_margin="16dp" android:layout_margin="16dp"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:nextFocusLeft="@id/btnApply"
android:nextFocusRight="@id/btnCancel"
android:text="취소하기" android:text="취소하기"
android:textSize="24sp" /> android:textSize="24sp" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<FrameLayout
android:id="@+id/serverSettingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/custom_border"
android:visibility="invisible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="서버 설정"
android:textSize="34sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="표시명"
android:textSize="24sp"
/>
<EditText
android:id="@+id/editDisplayName"
android:layout_width="1000px"
android:layout_height="wrap_content"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="인증 사용자명"
android:textSize="24sp"
/>
<EditText
android:id="@+id/editSipId"
android:layout_width="1000px"
android:layout_height="wrap_content"
android:inputType="phone"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="인증 암호"
android:textSize="24sp"
/>
<EditText
android:id="@+id/editSipPassword"
android:layout_width="1000px"
android:layout_height="wrap_content"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="SIP 서버주소"
android:textSize="24sp"
/>
<EditText
android:id="@+id/editSipServer"
android:layout_width="1000px"
android:layout_height="wrap_content"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="트랜스포트"
android:textSize="24sp"
/>
<RadioGroup
android:id="@+id/radioGroupTransport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingBottom="16dp">
<RadioButton
android:id="@+id/radioTransportUdp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingRight="15dp"
android:nextFocusUp="@id/editSipServer"
android:text="UDP"
android:textSize="24sp" />
<RadioButton
android:id="@+id/radioTransportTcp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingRight="15dp"
android:nextFocusUp="@id/editSipServer"
android:text="TCP"
android:textSize="24sp" />
<RadioButton
android:id="@+id/radioTransportTls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingRight="15dp"
android:nextFocusUp="@id/editSipServer"
android:text="TLS"
android:textSize="24sp" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="미디어 암호화"
android:textSize="24sp"
/>
<RadioGroup
android:id="@+id/radioGroupEncrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingBottom="16dp">
<RadioButton
android:id="@+id/radioEncryptNone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingRight="15dp"
android:nextFocusDown="@id/btnApplyServer"
android:text="사용안함"
android:textSize="24sp" />
<RadioButton
android:id="@+id/radioEncryptSrtp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:paddingRight="15dp"
android:nextFocusDown="@id/btnApplyServer"
android:text="SRTP"
android:textSize="24sp" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnApplyServer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:nextFocusUp="@id/radioEncryptNone"
android:nextFocusLeft="@id/btnApplyServer"
android:nextFocusRight="@id/btnCancelServer"
android:text="적용하기"
android:textSize="24sp" />
<Button
android:id="@+id/btnCancelServer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:nextFocusUp="@id/radioEncryptNone"
android:nextFocusLeft="@id/btnApplyServer"
android:nextFocusRight="@id/btnCancelServer"
android:text="취소하기"
android:textSize="24sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/volumeSettingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/custom_border"
android:visibility="invisible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="볼륨 설정"
android:textSize="34sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp">
<TextView
android:layout_width="200px"
android:layout_height="wrap_content"
android:text="마이크볼륨"
android:textSize="24sp"
/>
<SeekBar
android:id="@+id/seekbarMicVolume"
android:layout_width="600px"
android:max="24"
android:layout_height="wrap_content"
android:nextFocusUp="@id/seekbarMicVolume"
android:nextFocusLeft="@id/seekbarMicVolume"
android:nextFocusRight="@id/seekbarMicVolume"
android:textSize="24sp" />
<TextView
android:id="@+id/textMicVolume"
android:layout_width="200px"
android:layout_marginLeft="30px"
android:layout_height="wrap_content"
android:text=""
android:textSize="36sp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnDoneVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:nextFocusUp="@id/seekbarMicVolume"
android:nextFocusLeft="@id/btnDoneVolume"
android:nextFocusRight="@id/btnDoneVolume"
android:nextFocusDown="@id/btnDoneVolume"
android:text="돌아가기"
android:textSize="24sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/layoutSettingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/custom_border"
android:visibility="invisible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="레이아웃 설정"
android:textSize="34sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType1"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="155px"
android:layout_marginTop="140px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout1"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType10"
android:nextFocusRight="@id/layoutType2"
android:nextFocusUp="@id/layoutBackward"
android:nextFocusDown="@id/layoutType6" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType2"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="495px"
android:layout_marginTop="140px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout2"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType1"
android:nextFocusRight="@id/layoutType3"
android:nextFocusUp="@id/layoutBackward"
android:nextFocusDown="@id/layoutType7" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType3"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="835px"
android:layout_marginTop="140px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout3"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType2"
android:nextFocusRight="@id/layoutType4"
android:nextFocusUp="@id/layoutBackward"
android:nextFocusDown="@id/layoutType8" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType4"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="1175px"
android:layout_marginTop="140px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout4"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType3"
android:nextFocusRight="@id/layoutType5"
android:nextFocusUp="@id/layoutBackward"
android:nextFocusDown="@id/layoutType9" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType5"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="1515px"
android:layout_marginTop="140px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout5"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType4"
android:nextFocusRight="@id/layoutType6"
android:nextFocusUp="@id/layoutBackward"
android:nextFocusDown="@id/layoutType10" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType6"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="155px"
android:layout_marginTop="420px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout6"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType5"
android:nextFocusRight="@id/layoutType7"
android:nextFocusUp="@id/layoutType1"
android:nextFocusDown="@id/layoutBackward" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType7"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="495px"
android:layout_marginTop="420px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout7"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType6"
android:nextFocusRight="@id/layoutType8"
android:nextFocusUp="@id/layoutType2"
android:nextFocusDown="@id/layoutBackward" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType8"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="835px"
android:layout_marginTop="420px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout8"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType7"
android:nextFocusRight="@id/layoutType9"
android:nextFocusUp="@id/layoutType3"
android:nextFocusDown="@id/layoutBackward" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType9"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="1175px"
android:layout_marginTop="420px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout9"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType8"
android:nextFocusRight="@id/layoutType10"
android:nextFocusUp="@id/layoutType4"
android:nextFocusDown="@id/layoutBackward" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutType10"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="1515px"
android:layout_marginTop="420px"
android:background="@drawable/bg_button_selector_2"
android:drawableTop="@drawable/layout10"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutType9"
android:nextFocusRight="@id/layoutType1"
android:nextFocusUp="@id/layoutType5"
android:nextFocusDown="@id/layoutBackward" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/layoutBackward"
android:layout_width="250px"
android:layout_height="210px"
android:layout_marginLeft="835px"
android:layout_marginTop="780px"
android:background="@drawable/bg_button_selector"
android:drawableTop="@drawable/backward"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:padding="32dp"
android:nextFocusLeft="@id/layoutBackward"
android:nextFocusRight="@id/layoutBackward"
android:nextFocusUp="@id/layoutType8"
android:nextFocusDown="@id/layoutType3" />
</FrameLayout>
</RelativeLayout> </RelativeLayout>