마이크 볼륨 설정 추가

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'
minSdkVersion 29
targetSdkVersion 35
versionCode = 100
versionName = '1.0.0'
versionCode = 104
versionName = '1.0.4'
externalNativeBuild {
cmake {
cFlags '-DHAVE_INTTYPES_H -lstdc++'
@ -45,6 +45,7 @@ android {
// minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
android.applicationVariants.all { variant ->

View File

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

View File

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

View File

@ -1785,6 +1785,18 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_plus_Api_call_1set_1video_1source
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(
JNIEnv *env, jobject obj, jlong call)
{
@ -1808,6 +1820,36 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_plus_Api_cmd_1exec(
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)
{
(void)obj;

View File

@ -98,6 +98,7 @@ object Api {
external fun call_state(callp: Long): Int
external fun call_has_video(callp: Long): Boolean
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_mute(callp: Long, mute: Boolean) // added by ritoseo
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 set_audio_source(): String
external fun audio_codecs(): String
external fun video_codecs(): String

View File

@ -35,14 +35,12 @@ import android.telecom.TelecomManager
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.util.JsonReader
import android.util.Size
import android.view.LayoutInflater
import android.view.View
import android.widget.RemoteViews
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.ColorRes
import androidx.annotation.Keep
import androidx.annotation.StringRes
@ -63,11 +61,8 @@ import org.json.JSONObject
import java.io.File
import java.io.IOException
import java.net.InetAddress
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.time.Clock
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.concurrent.schedule
@ -269,7 +264,6 @@ class BaresipService: Service() {
}
}
sipReqeustReceiver = object : BroadcastReceiver() {
fun sendContactList() {
val resArr : JSONArray = JSONArray()
@ -570,11 +564,17 @@ class BaresipService: Service() {
if(param != null) {
val json = JSONObject(param)
val device_name = json.getString("device_name")
val mic_volume = json.getString("mic_volume")
val display_type = json.getString("display_type")
Config.replaceVariable("device_name", 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)
BaresipService.farViewDisplayId = display_type.toInt()
Config.save()
@ -759,14 +759,28 @@ class BaresipService: Service() {
}
val scope = CoroutineScope(Dispatchers.Default)
var captureCount = 0
var captureCount : Long = 0
var running = true
val job = scope.launch {
println("camera capture manager is running...")
Utils.deleteFiles("/mnt/obb", "jpg")
var updateDate = LocalDateTime.now()
var cameraState = -1
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()) {
try {
val nearBuf = Utils.readFileToByteBuffer("/mnt/obb/camera_near.rgb565")
@ -970,25 +984,88 @@ class BaresipService: Service() {
val camera1 = Utils.checkCameraConnection(0)
val camera2 = Utils.checkCameraConnection(1)
val prevCamera1 = Utils.propertyGet("sys.ritosip.camera1.status")
Utils.propertySet("sys.ritosip.camera1.status", camera1)
//call.setVideoSource(!BaresipService.cameraFront)
val prevCamera2 = Utils.propertyGet("sys.ritosip.camera2.status")
Utils.propertySet("sys.ritosip.camera2.status", camera2)
if(camera2 != prevCamera2) {
if(camera1 != "plugin" && camera2 != "plugin" && cameraState != 0) {
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
if(call != null) {
if(camera2 == "plugin") {
BaresipService.cameraFront = false; // Contents Input
call.setVideoSource(false)
} else {
BaresipService.cameraFront = true; // Camera Input
call.setVideoFake()
cameraState = 0
}
}
} 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) {
@ -1000,15 +1077,17 @@ class BaresipService: Service() {
val usbMicExist = Utils.checkUsbMicrophone()
if(usbMicExist) {
Utils.propertySet("sys.ritosip.usb.microphone", "connected")
if(audioInputDevice == "usb") {
Utils.propertySet("sys.rito.audio.input.device", "usb")
} else {
Utils.propertySet("sys.rito.audio.input.device", "codec")
}
// if(audioInputDevice == "usb") {
// Utils.propertySet("sys.rito.audio.input.device", "usb")
// } else {
// Utils.propertySet("sys.rito.audio.input.device", "codec")
// }
} else {
Utils.propertySet("sys.ritosip.usb.microphone", "disconnected")
}
Utils.setAudioSourceDevice(audioInputDevice)
delay(100) // 1초마다 실행
val now = LocalDateTime.now()
@ -1051,7 +1130,7 @@ class BaresipService: Service() {
if (farFilePath.length > 0)
Utils.propertySet("sys.ritosip.far_screen_file", farFilePath)
} else {
Utils.propertySet("sys.ritosip.near_screen_file", "")
//Utils.propertySet("sys.ritosip.near_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.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++
delay(100)
@ -1161,7 +1288,7 @@ class BaresipService: Service() {
CallHistoryNew.save()
}
val value = Utils.readNumberFromFile("/sdcard/Documents/sip_total_duration")
val value = Utils.readNumberFromFile("/sdcard/Documents/sip_total_duration.txt")
if(value != null) {
BaresipService.totalDurationSeconds = value
}
@ -1225,6 +1352,8 @@ class BaresipService: Service() {
applyTransportConfiguration()
Utils.setMicVolumeByMix(BaresipService.micVolume)
}
"Start Content Observer" -> {
@ -1826,7 +1955,9 @@ class BaresipService: Service() {
/* Added by ritoseo */
val duration = call.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()) {
@ -2267,11 +2398,15 @@ class BaresipService: Service() {
}
private fun setCallVolume() {
println("Call volume setting!")
callVolume = 0
if (callVolume != 0)
for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) {
origVolume[streamType] = am.getStreamVolume(streamType)
val maxVolume = am.getStreamMaxVolume(streamType)
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 " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
}
@ -2548,6 +2683,26 @@ class BaresipService: Service() {
var nearViewDisplayId = 2
var farViewLayout : 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 {
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)
}
fun setVideoFake(): Int {
return Api.call_set_video_fake(callp)
}
fun hold(): Boolean {
return Api.call_hold(callp, true) == 0
}

View File

@ -211,6 +211,14 @@ object Config {
*/
/* 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)
videoSizes.add(size.toString())
/********************/
@ -225,12 +233,15 @@ object Config {
Size(videoSize.substringBefore("x").toInt(),
videoSize.substringAfter("x").toInt())
}
BaresipService.videoSize = Size(1280, 720)
//BaresipService.videoSize = Size(640, 480)
config = "${config}video_size " +
"${BaresipService.videoSize.width}x${BaresipService.videoSize.height}\n"
/* 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_bandwidth 512-8192\n"
/********************/

View File

@ -11,6 +11,7 @@ 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.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Rect
@ -19,10 +20,8 @@ import android.media.AudioManager
import android.media.MediaActionSound
import android.net.Uri
import android.os.*
import android.os.StrictMode.VmPolicy
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.MediaStore.Audio.Radio
import android.text.InputType
import android.text.TextWatcher
import android.util.TypedValue
@ -48,14 +47,18 @@ 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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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 kotlin.collections.HashMap
import kotlin.system.exitProcess
@ -138,6 +141,11 @@ class MainActivity : AppCompatActivity() {
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 var callHandler: Handler = Handler(Looper.getMainLooper())
private var callRunnable: Runnable? = null
@ -191,6 +199,9 @@ class MainActivity : AppCompatActivity() {
lateinit var navUpList : 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 {
if(!isKeyboardVisible) {
val currentFocusView = currentFocus
@ -214,6 +225,24 @@ class MainActivity : AppCompatActivity() {
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)) {
@ -223,12 +252,22 @@ class MainActivity : AppCompatActivity() {
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.editDns ||
currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword
) {
if(imm.isActive) {
imm.showSoftInput(currentFocusView, InputMethodManager.SHOW_IMPLICIT)
imm.restartInput(currentFocusView)
@ -242,6 +281,9 @@ class MainActivity : AppCompatActivity() {
if(navUpList.contains(currentFocusView) || navDownList.contains(currentFocusView)) {
found = true
}
if(navUpServerList.contains(currentFocusView) || navDownServerList.contains(currentFocusView)) {
found = true
}
if(found) {
return true
@ -253,6 +295,12 @@ class MainActivity : AppCompatActivity() {
currentFocusView == binding.editDns) {
return true
}
if(currentFocusView == binding.editDisplayName ||
currentFocusView == binding.editSipId ||
currentFocusView == binding.editSipPassword) {
return true
}
}
}
} else {
@ -389,6 +437,8 @@ class MainActivity : AppCompatActivity() {
setupKeyboardVisibilityListener()
Utils.propertySet("sys.ritosip.version", getAppVersion(this))
// Must be done after view has been created
this.setShowWhenLocked(true)
this.setTurnScreenOn( true)
@ -453,10 +503,213 @@ class MainActivity : AppCompatActivity() {
// 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.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()
@ -530,12 +783,87 @@ class MainActivity : AppCompatActivity() {
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()
}
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)
@ -1179,11 +1507,12 @@ class MainActivity : AppCompatActivity() {
else
getString(R.string.audio_permissions)
) { requestPermissionsLauncher.launch(permissions) }
else
startBaresip()
// else
// startBaresip()
}
}
startBaresip()
addVideoLayoutViews()
if (!BaresipService.isServiceRunning) {
@ -1192,7 +1521,7 @@ class MainActivity : AppCompatActivity() {
Utils.getFileContents(filesDir.absolutePath + "/accounts")!!,
Charsets.UTF_8
).lines().toMutableList()
askPasswords(accounts)
//askPasswords(accounts)
} else {
// Baresip is started for the first time
requestPermissionsLauncher.launch(permissions)
@ -1201,6 +1530,14 @@ class MainActivity : AppCompatActivity() {
} // 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")
@ -2055,6 +2392,7 @@ class MainActivity : AppCompatActivity() {
hangupButton.performClick()
}
videoLayout.addView(hb)
hb.visibility = View.INVISIBLE // by ritoseo
// Info Button
val ib = ImageButton(this)
@ -2354,13 +2692,50 @@ class MainActivity : AppCompatActivity() {
}
when (event?.scanCode) {
SCANCODE_OPTION -> {
val dialog = findViewById<FrameLayout>(R.id.dialogLayout)
if(dialog.isVisible) {
// 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.dialogButtonImg1).requestFocus()
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)
@ -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>) {
if (accounts.isNotEmpty()) {
val account = accounts.removeAt(0)
@ -3315,8 +3703,8 @@ class MainActivity : AppCompatActivity() {
callVideoButton.visibility = View.INVISIBLE
callVideoButton.isEnabled = false
switchVideoLayout(true)
hangupButton.visibility = View.VISIBLE
hangupButton.isEnabled = 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
@ -3470,7 +3858,10 @@ class MainActivity : AppCompatActivity() {
//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
@ -3501,8 +3892,10 @@ class MainActivity : AppCompatActivity() {
securityButton.visibility = View.INVISIBLE
diverter.visibility = View.GONE
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
answerVideoButton.visibility = View.INVISIBLE
rejectButton.visibility = View.INVISIBLE
@ -3528,6 +3921,8 @@ class MainActivity : AppCompatActivity() {
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
@ -3603,8 +3998,9 @@ class MainActivity : AppCompatActivity() {
switchVideoLayout(true)
hangupButton.visibility = View.VISIBLE
hangupButton.isEnabled = 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

View File

@ -14,6 +14,8 @@ import android.graphics.Bitmap.createScaledBitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
@ -49,6 +51,9 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.*
import java.lang.reflect.Method
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 {
return try {
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)
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver")
val process = Runtime.getRuntime().exec("ritosysc shell-order=killall android.hardware.audio.service")
@ -1267,6 +1328,22 @@ object Utils {
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() {
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()
@ -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) {
propertySet("sys.rito.audio.input.device", dev)
//val process = Runtime.getRuntime().exec("ritosysc shell-order=killall audioserver")
@ -1315,7 +1414,17 @@ object Utils {
val file = File("/d/hdmirx/status") // 파일 경로 설정
file.bufferedReader().use { reader ->
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) {
val file = File("/sys/module/lt6911uxc/parameters/status") // 파일 경로 설정
@ -1334,13 +1443,21 @@ object Utils {
fun saveNumberToFile(path: String, number: Int) {
val file = File(path)
try {
//println("파일 존재 여부: ${file.exists()}, 디렉토리 여부: ${file.isDirectory}, 파일 여부: ${file.isFile}")
if(!file.exists()) {
file.createNewFile()
}
file.writeText(number.toString())
Log.d("FileSave", "숫자 저장 완료: $number")
RandomAccessFile(path, "rw").use { raf ->
raf.fd.sync() // eMMC에 확실히 기록
raf.close()
}
file.setWritable(true, false)
} catch (e: IOException) {
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" />
</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
android:id="@+id/defaultLayout"
android:layout_width="match_parent"
@ -662,6 +671,228 @@
</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
android:id="@+id/networkSettingLayout"
android:layout_width="match_parent"
@ -761,6 +992,8 @@
android:layout_margin="16dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:nextFocusLeft="@id/btnApply"
android:nextFocusRight="@id/btnCancel"
android:text="적용하기"
android:textSize="24sp" />
@ -771,10 +1004,549 @@
android:layout_margin="16dp"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:nextFocusLeft="@id/btnApply"
android:nextFocusRight="@id/btnCancel"
android:text="취소하기"
android:textSize="24sp" />
</LinearLayout>
</LinearLayout>
</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>