ritoseo 96aefe8738 리모컨 네트워크 설정 처리 적용
setProperty로 네트워크 상태, account 상태 설정 처리
2025-04-11 17:05:15 +09:00

2593 lines
117 KiB
Kotlin

package com.tutpro.baresip.plus
import android.Manifest
import android.annotation.SuppressLint
import android.app.*
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothHeadset
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
import android.database.ContentObserver
import android.graphics.BitmapFactory
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.media.*
import android.media.AudioManager.MODE_IN_COMMUNICATION
import android.media.AudioManager.MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_SILENT
import android.media.audiofx.AcousticEchoCanceler
import android.media.audiofx.AutomaticGainControl
import android.media.audiofx.NoiseSuppressor
import android.net.*
import android.net.wifi.WifiManager
import android.os.*
import android.os.Build.VERSION
import android.provider.ContactsContract
import android.provider.Settings
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
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.media.AudioAttributesCompat
import androidx.media.AudioFocusRequestCompat
import androidx.media.AudioManagerCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONArray
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
import kotlin.math.roundToInt
class BaresipService: Service() {
internal lateinit var intent: Intent
private lateinit var am: AudioManager
private lateinit var rt: Ringtone
private lateinit var nt: Ringtone
private lateinit var nm: NotificationManager
private lateinit var snb: NotificationCompat.Builder
private lateinit var cm: ConnectivityManager
private lateinit var pm: PowerManager
private lateinit var wm: WifiManager
private lateinit var tm: TelecomManager
private lateinit var btm: BluetoothManager
private lateinit var vibrator: Vibrator
private lateinit var partialWakeLock: PowerManager.WakeLock
private lateinit var proximityWakeLock: PowerManager.WakeLock
private lateinit var wifiLock: WifiManager.WifiLock
private lateinit var bluetoothReceiver: BroadcastReceiver
private lateinit var hotSpotReceiver: BroadcastReceiver
private lateinit var androidContactsObserver: ContentObserver
private lateinit var stopState: String
private lateinit var quitTimer: CountDownTimer
private var vbTimer: Timer? = null
private var origVolume = mutableMapOf<Int, Int>()
private var linkAddresses = mutableMapOf<String, String>()
private var activeNetwork: Network? = null
private var allNetworks = mutableSetOf<Network>()
private var hotSpotIsEnabled = false
private var hotSpotAddresses = mapOf<String, String>()
private var mediaPlayer: MediaPlayer? = null
private var androidContactsObserverRegistered = false
private var isServiceClean = false
private lateinit var sipReqeustReceiver: BroadcastReceiver
@SuppressLint("WakelockTimeout")
override fun onCreate() {
super.onCreate()
Log.d(TAG, "BaresipService onCreate")
intent = Intent("com.tutpro.baresip.plus.EVENT")
intent.setPackage("com.tutpro.baresip.plus")
filesPath = filesDir.absolutePath
pName = packageName
am = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val ntUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,
RingtoneManager.TYPE_NOTIFICATION)
nt = RingtoneManager.getRingtone(applicationContext, ntUri)
val rtUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,
RingtoneManager.TYPE_RINGTONE)
rt = RingtoneManager.getRingtone(applicationContext, rtUri)
nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannels()
snb = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
pm = getSystemService(Context.POWER_SERVICE) as PowerManager
vibrator = if (VERSION.SDK_INT >= 31) {
val vibratorManager = applicationContext.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
vibratorManager.defaultVibrator
} else {
@Suppress("DEPRECATION")
applicationContext.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
}
// This is needed to keep service running also in Doze Mode
partialWakeLock = pm.run {
newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"com.tutpro.baresip:partial_wakelog"
).apply {
acquire()
}
}
cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val builder = NetworkRequest.Builder()
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
cm.registerNetworkCallback(
builder.build(),
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.d(TAG, "Network $network is available")
if (network !in allNetworks)
allNetworks.add(network)
val netInfo = Utils.getEthernetInfo(applicationContext)
if(netInfo.get("ip") != null) {
deviceIpAddress = netInfo.get("ip")!!
}
Utils.propertySet("sys.ritosip.ipv4.address", deviceIpAddress)
if(netInfo.get("netmask") != null) {
Utils.propertySet("sys.ritosip.ipv4.netmask", netInfo.get("netmask")!!)
}
if(netInfo.get("gateway") != null) {
Utils.propertySet("sys.ritosip.ipv4.gateway", netInfo.get("gateway")!!)
}
if(netInfo.get("dns") != null) {
Utils.propertySet("sys.ritosip.ipv4.dns", netInfo.get("dns")!!)
}
Utils.checkNetworkIpType()
val newIntent = Intent()
newIntent.setClassName("kr.co.rito.ritosip", "com.tutpro.baresip.plus.MainActivity")
newIntent.flags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", "network available")
//newIntent.putExtra("address", netInfo.get("ip"))
newIntent.putExtra("address", deviceIpAddress)
startActivity(newIntent)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
Log.d(TAG, "Network $network is losing after $maxMsToLive ms")
}
override fun onLost(network: Network) {
super.onLost(network)
Log.d(TAG, "Network $network is lost")
if (network in allNetworks)
allNetworks.remove(network)
if (isServiceRunning)
updateNetwork()
}
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
super.onCapabilitiesChanged(network, caps)
Log.d(TAG, "Network $network capabilities changed: $caps")
if (network !in allNetworks)
allNetworks.add(network)
if (isServiceRunning)
updateNetwork()
}
override fun onLinkPropertiesChanged(network: Network, props: LinkProperties) {
super.onLinkPropertiesChanged(network, props)
Log.d(TAG, "Network $network link properties changed: $props")
if (network !in allNetworks)
allNetworks.add(network)
if (isServiceRunning)
updateNetwork()
}
}
)
fun aorGenerateHistory(aor: String) {
CallsActivity.uaHistory.clear()
for (i in BaresipService.callHistory.indices.reversed()) {
val h = BaresipService.callHistory[i]
if (h.aor == aor) {
val direction: Int = if (h.direction == "in") {
if (h.startTime != null) {
R.drawable.call_down_green
} else {
if (h.rejected)
R.drawable.call_down_red
else
R.drawable.call_missed_in
}
} else {
if (h.startTime != null) {
R.drawable.call_up_green
} else {
if (h.rejected)
R.drawable.call_up_red
else
R.drawable.call_missed_out
}
}
//println("[History] ${h.startTime?.toZonedDateTime()} ${h.peerUri} ${h.direction}")
CallsActivity.uaHistory.add(
CallRow(
h.aor, h.peerUri, direction, h.startTime, h.stopTime,
h.recording
)
)
}
}
}
sipReqeustReceiver = object : BroadcastReceiver() {
fun sendContactList() {
val resArr : JSONArray = JSONArray()
for(idx in 0..baresipContacts.size - 1) {
val res : JSONObject = JSONObject()
val row = baresipContacts[idx]
var directionStr : String
res.put("id", row.id)
res.put("name", row.name)
res.put("favorite", row.favorite)
res.put("uri", row.uri)
resArr.put(res)
}
val responseIntent = Intent("kr.co.rito.ritosip.RESPONSE")
responseIntent.putExtra("req", "contact_list")
responseIntent.putExtra("data", resArr.toString())
responseIntent.setPackage("kr.co.rito.sipsvc");
sendBroadcast(responseIntent)
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "kr.co.rito.ritosip.REQUEST") {
val req = intent?.getStringExtra("request")
println("[RITO] GOT Req : ${req}")
if(req == "call_history") {
if(uas.size > 0) {
aorGenerateHistory(BaresipService.uas[0].account.aor)
}
val resArr : JSONArray = JSONArray()
for(idx in 0..CallsActivity.uaHistory.size - 1) {
val res : JSONObject = JSONObject()
val row = CallsActivity.uaHistory[idx]
var directionStr : String
if(row.direction == R.drawable.call_down_green || row.direction == R.drawable.call_down_red || row.direction == R.drawable.call_missed_in) {
directionStr = "in"
} else {
directionStr = "out"
}
res.put("peerUri", row.peerUri)
res.put("direction", directionStr)
res.put("stopTime", row.stopTime.toZonedDateTime())
resArr.put(res)
}
val responseIntent = Intent("kr.co.rito.ritosip.RESPONSE")
responseIntent.putExtra("req", req)
responseIntent.putExtra("data", resArr.toString())
responseIntent.setPackage("kr.co.rito.sipsvc");
context?.sendBroadcast(responseIntent)
} else if(req == "contact_list") {
sendContactList()
} else if(req == "save_address") {
val param = intent?.getStringExtra("param")
var result = "failed"
if(param != null) {
val json = JSONObject(param)
val name = json.getString("name")
//val favorite = json.getString("favorite")
val uri = json.getString("uri")
val contact = Contact.BaresipContact(name, uri, 0, SystemClock.elapsedRealtime(), false)
Contact.addBaresipContact(contact)
Contact.saveBaresipContacts()
result = "success"
}
val responseIntent = Intent("kr.co.rito.ritosip.RESPONSE")
responseIntent.putExtra("req", req)
responseIntent.putExtra("data", result)
responseIntent.setPackage("kr.co.rito.sipsvc")
context?.sendBroadcast(responseIntent)
sendContactList()
} else if(req == "remove_address") {
val param = intent?.getStringExtra("param")
var result = "failed"
if(param != null) {
val json = JSONObject(param)
val uri = json.getString("uri")
val iterator = baresipContacts.iterator()
while (iterator.hasNext()) {
val it = iterator.next()
if(it.uri == uri) {
iterator.remove()
Contact.saveBaresipContacts()
result = "success"
}
}
}
val responseIntent = Intent("kr.co.rito.ritosip.RESPONSE")
responseIntent.putExtra("req", req)
responseIntent.putExtra("data", result)
responseIntent.setPackage("kr.co.rito.sipsvc");
context?.sendBroadcast(responseIntent)
sendContactList()
}
if(req == "start_call") {
if(uas.size > 0) {
val param = intent?.getStringExtra("param")
if (param != null) {
val json = JSONObject(param)
uas[0].account.resumeUri = json.getString("number")
} else {
uas[0].account.resumeUri = "7779"
}
println("resumeUri : ${uas[0].account.resumeUri}")
val newIntent = Intent(context, MainActivity::class.java)
newIntent.flags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", "video call")
newIntent.putExtra("peer", uas[0].account.resumeUri)
startActivity(newIntent)
}
} else if(req == "stop_call") {
if(uas.size > 0) {
val newIntent = Intent(context, MainActivity::class.java)
newIntent.flags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", "hangup")
startActivity(newIntent)
}
} else if(req == "audio_mute") {
Api.calls_mute(true)
isMicMuted = true
} else if(req == "audio_unmute") {
Api.calls_mute(false)
isMicMuted = false
} else if(req == "video_mute") {
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
call?.setVideoMute(true)
isVideoMuted = true
}
} else if(req == "video_unmute") {
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
call?.setVideoMute(false)
isVideoMuted = false
}
} else if(req == "audio_source") {
val param = intent?.getStringExtra("param")
if(param != null) {
try {
val json = JSONObject(param)
var device = "usb"
val source_target = json.getString("target")
if(source_target == "aux") {
device = "codec"
}
Utils.setAudioSourceDevice(device)
audioInputDevice = device
} catch(e : Exception) {
e.printStackTrace()
}
}
} else if(req == "set_account") {
UserAgent.deleteAllUas()
val param = intent?.getStringExtra("param")
if(param != null) {
try {
val json = JSONObject(param)
val display_name = json.getString("display_name")
val account_name = json.getString("account_name")
val password = json.getString("password")
val server_address = json.getString("server_address")
val transport = json.getString("transport")
val media_encryption = json.getString("media_encryption")
//var param = "transport=tls"
val ua = UserAgent.uaAlloc("sip:${account_name}@${server_address}")
if (ua != null) {
val acc = ua.account
acc.nickName = display_name
acc.displayName = acc.nickName
//acc.authUser = account_name
//acc.authPass = password
Api.account_set_display_name(acc.accp, display_name)
Api.account_set_auth_user(acc.accp, account_name)
acc.authUser = Api.account_auth_user(acc.accp)
Api.account_set_auth_pass(acc.accp, password)
acc.authPass = Api.account_auth_pass(acc.accp)
if(BaresipService.callAnswerMode == "manual") {
Api.account_set_answermode(acc.accp, Api.ANSWERMODE_MANUAL)
acc.answerMode = Api.ANSWERMODE_MANUAL
} else {
Api.account_set_answermode(acc.accp, Api.ANSWERMODE_AUTO)
acc.answerMode = Api.ANSWERMODE_AUTO
}
/*
Api.log_level_set(0)
Log.logLevelSet(0)
Api.uag_enable_sip_trace(true)
*/
Api.ua_register(ua.uap)
if (transport != null && transport.length > 0) {
Config.replaceVariable("prefer_transport", transport)
BaresipService.preferTransport = transport
Config.save()
}
applyTransportConfiguration()
if (media_encryption != null && media_encryption.length > 0) {
var mediaEnc = ""
if(media_encryption == "srtpo") {
mediaEnc = "srtp"
} else if(media_encryption == "srtpm") {
mediaEnc = "srtp-mand"
}
if (mediaEnc != acc.mediaEnc) {
if (Api.account_set_mediaenc(acc.accp, mediaEnc) == 0) {
acc.mediaEnc = Api.account_mediaenc(acc.accp)
println("[RITO] New mediaenc is ${acc.mediaEnc}")
} else {
Log.e(TAG, "Setting of mediaenc $mediaEnc failed")
}
}
}
/*
Api.config_verify_server_set(false)
Api.config_transport_server_set("tls")
Api.uag_reset_transp(true, true)
*/
Api.account_set_regint(acc.accp, REGISTRATION_INTERVAL)
acc.regint = REGISTRATION_INTERVAL
//UserAgent.apply { ua }
println("[RITO] set_account : ${ua.account.print()}")
BaresipService.uas[0] = ua
//Api.ua_register(ua.uap)
AccountsActivity.generateAccounts()
AccountsActivity.saveAccounts()
//UserAgent.register()
}
} catch(e : Exception) {
e.printStackTrace()
}
}
} else if(req == "layout_setting") {
val param = intent?.getStringExtra("param")
if(param != null) {
val json = JSONObject(param)
val layout = json.getString("layout")
if(layout == "layout1") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_최대_근거리_우하단
} else if(layout == "layout2") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_최대_근거리_우상단
} else if(layout == "layout3") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌상단
} else if(layout == "layout4") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌하단
} else if(layout == "layout5") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_대상단_근거리_소하단
} else if(layout == "layout6") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_대좌단_근거리_소우단
} else if(layout == "layout7") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_대하단_근거리_소상단
} else if(layout == "layout8") {
displaySplitMode = DISPLAY_SPLIT_MODE_원거리_반좌단_근거리_반우단
}
sendActivityAction("update layout")
}
} else if(req == "misc_setting") {
val param = intent?.getStringExtra("param")
if(param != null) {
val json = JSONObject(param)
val device_name = json.getString("device_name")
Config.replaceVariable("device_name", device_name)
BaresipService.deviceName = device_name
Config.save()
sendActivityAction("update info")
}
}
}
}
}
this.registerReceiver(sipReqeustReceiver,
IntentFilter("kr.co.rito.ritosip.REQUEST"))
wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
hotSpotIsEnabled = Utils.isHotSpotOn(wm)
hotSpotReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context, intent: Intent) {
val action = intent.action
if ("android.net.wifi.WIFI_AP_STATE_CHANGED" == action) {
val state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0)
if (WifiManager.WIFI_STATE_ENABLED == state % 10) {
if (hotSpotIsEnabled) {
Log.d(TAG, "HotSpot is still enabled")
} else {
Log.d(TAG, "HotSpot is enabled")
hotSpotIsEnabled = true
Timer().schedule(1000) {
hotSpotAddresses = Utils.hotSpotAddresses()
Log.d(TAG, "HotSpot addresses $hotSpotAddresses")
if (hotSpotAddresses.isNotEmpty()) {
var reset = false
for ((k, v) in hotSpotAddresses)
if (afMatch(k))
if (Api.net_add_address_ifname(k, v) != 0)
Log.e(TAG, "Failed to add $v address $k")
else
reset = true
if (reset)
Timer().schedule(2000) {
updateNetwork()
}
} else {
Log.w(TAG, "Could not get hotspot addresses")
}
}
}
} else {
if (!hotSpotIsEnabled) {
Log.d(TAG, "HotSpot is still disabled")
} else {
Log.d(TAG, "HotSpot is disabled")
hotSpotIsEnabled = false
if (hotSpotAddresses.isNotEmpty()) {
for ((k, _) in hotSpotAddresses)
if (Api.net_rm_address(k) != 0)
Log.e(TAG, "Failed to remove address $k")
hotSpotAddresses = mapOf()
updateNetwork()
}
}
}
}
}
}
this.registerReceiver(hotSpotReceiver,
IntentFilter("android.net.wifi.WIFI_AP_STATE_CHANGED"))
tm = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
btm = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
btAdapter = btm.adapter
proximityWakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
"com.tutpro.baresip.plus:proximity_wakelock")
wifiLock = if (VERSION.SDK_INT < 29)
@Suppress("DEPRECATION")
wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Baresip+")
else
wm.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Baresip+")
wifiLock.setReferenceCounted(false)
bluetoothReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
when (intent.action) {
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_DISCONNECTED)
when (state) {
BluetoothHeadset.STATE_CONNECTED -> {
Log.d(TAG, "Bluetooth headset is connected")
if (audioFocusRequest != null)
startBluetoothSco(applicationContext, 1000L, 3)
}
BluetoothHeadset.STATE_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset is disconnected")
if (audioFocusRequest != null)
stopBluetoothSco(applicationContext)
}
}
}
BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
when (state) {
BluetoothHeadset.STATE_AUDIO_CONNECTED -> {
Log.d(TAG, "Bluetooth headset audio is connected")
}
BluetoothHeadset.STATE_AUDIO_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset audio is disconnected")
}
}
}
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
val state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED)
when (state) {
AudioManager.SCO_AUDIO_STATE_CONNECTING -> {
Log.d(TAG, "Bluetooth headset SCO is connecting")
}
AudioManager.SCO_AUDIO_STATE_CONNECTED -> {
Log.d(TAG, "Bluetooth headset SCO is connected")
}
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset SCO is disconnected")
resetCallVolume()
}
AudioManager.SCO_AUDIO_STATE_ERROR -> {
Log.d(TAG, "Bluetooth headset SCO state ERROR")
}
}
}
}
}
}
if (btAdapter != null) {
val filter = IntentFilter()
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
this.registerReceiver(bluetoothReceiver, filter)
}
androidContactsObserver = object : ContentObserver(null) {
override fun onChange(self: Boolean) {
Log.d(TAG, "Android contacts change")
if (contactsMode != "baresip") {
Contact.loadAndroidContacts(this@BaresipService.applicationContext)
Contact.contactsUpdate()
}
}
}
stopState = "initial"
quitTimer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "Seconds remaining: ${millisUntilFinished / 1000}")
}
override fun onFinish() {
when (stopState) {
"initial" -> {
if (isServiceRunning)
baresipStop(true)
stopState = "force"
quitTimer.start()
}
"force" -> {
cleanService()
isServiceRunning = false
postServiceEvent(ServiceEvent("stopped", arrayListOf(""), System.nanoTime()))
stopSelf()
// exitProcess(0)
}
}
}
}
val scope = CoroutineScope(Dispatchers.Default)
var captureCount = 0
var running = true
val job = scope.launch {
println("camera capture manager is running...")
Utils.deleteFiles("/mnt/obb", "jpg")
var updateDate = LocalDateTime.now()
while (running) {
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")
val bufSize : Int = nearBuf.capacity() / 2
var width : Int = 1280
var height : Int = 720
if(bufSize == 1280 * 720) {
width = 1280
height = 720
} else if(bufSize == 1920 * 1080) {
width = 1920
height = 1080
}
Utils.saveRGB565AsJPG(
applicationContext,
nearBuf,
width,
height,
"/mnt/obb/camera_near.jpg"
)
} catch(e : Exception) {
e.printStackTrace()
}
}
if(File("/mnt/obb/camera_far.rgb565.done").exists() && !File("/mnt/obb/camera_far.jpg").exists()) {
try {
val farBuf = Utils.readFileToByteBuffer("/mnt/obb/camera_far.rgb565")
val bufSize : Int = farBuf.capacity() / 2
var width : Int = 1280
var height : Int = 720
if(bufSize == 1280 * 720) {
width = 1280
height = 720
} else if(bufSize == 1920 * 1080) {
width = 1920
height = 1080
}
Utils.saveRGB565AsJPG(
applicationContext,
farBuf,
width,
height,
"/mnt/obb/camera_far.jpg"
)
} catch(e : Exception) {
e.printStackTrace()
}
}
//delay(800) // 1초마다 실행
for(i in 1..8) {
var callExist = false
if(uas.size > 0) {
val ua = uas[0]
val account = ua.account
val call = ua.currentCall()
var sipaddr = ua.account.aor
sipaddr = sipaddr.removePrefix("sip:")
Utils.propertySet("sys.ritosip.sip.address", sipaddr)
if(ua.status == R.drawable.circle_yellow) {
Utils.propertySet("sys.ritosip.sip.status", "registering")
} else if(ua.status == R.drawable.circle_green) {
Utils.propertySet("sys.ritosip.sip.status", "registered")
} else if(ua.status == R.drawable.circle_red) {
Utils.propertySet("sys.ritosip.sip.status", "registering failed")
} else if(ua.status == R.drawable.circle_white) {
Utils.propertySet("sys.ritosip.sip.status", "unregistered")
}
if(account != null) {
Utils.propertySet("sys.ritosip.account.display_name", account.nickName)
Utils.propertySet("sys.ritosip.account.account_name", account.authUser)
Utils.propertySet("sys.ritosip.account.server_address", account.host())
Utils.propertySet("sys.ritosip.account.prefer_transport", preferTransport)
if(account.mediaEnc.startsWith("srtp", ignoreCase = true)) {
Utils.propertySet(
"sys.ritosip.account.media_encryption",
"srtp"
)
} else {
Utils.propertySet(
"sys.ritosip.account.media_encryption",
"none"
)
}
}
if (call != null) {
callExist = true
var audioMute = "unmute"
var videoMute = "unmute"
if(isMicMuted)
audioMute = "mute"
if(isVideoMuted)
videoMute = "mute"
Utils.propertySet("sys.ritosip.call.mute.audio", audioMute)
Utils.propertySet("sys.ritosip.call.mute.video", videoMute)
Utils.propertySet("sys.ritosip.call.status", "calling")
Utils.propertySet("sys.ritosip.call.number", call.peerUri)
var stats = call.stats("audio")
if (stats != "") {
//println("stats audio = ${stats}")
val parts = stats.split(",") as ArrayList
if (parts[2] == "0/0") {
parts[2] = "?/?"
parts[3] = "?/?"
parts[4] = "?/?"
}
Utils.propertySet("sys.ritosip.call.audio.current_bitrate", parts[0])
Utils.propertySet("sys.ritosip.call.audio.average_bitrate", parts[1])
Utils.propertySet("sys.ritosip.call.audio.packets", parts[2])
Utils.propertySet("sys.ritosip.call.audio.lost", parts[3])
Utils.propertySet("sys.ritosip.call.audio.jitter", parts[4])
}
val duration = call.duration()
Utils.propertySet("sys.ritosip.call.duration", duration.toString())
val codecs = call.audioCodecs()
if(codecs.length >= 2) {
val txCodec = codecs.split(',')[0].split("/")
val rxCodec = codecs.split(',')[1].split("/")
Utils.propertySet("sys.ritosip.call.audio.txCodec", txCodec[0])
Utils.propertySet("sys.ritosip.call.audio.rxCodec", rxCodec[0])
}
stats = call.stats("video")
if (stats != "") {
//println("stats video = ${stats}")
val parts = stats.split(",")
Utils.propertySet("sys.ritosip.call.video.current_bitrate", parts[0])
Utils.propertySet("sys.ritosip.call.video.average_bitrate", parts[1])
Utils.propertySet("sys.ritosip.call.video.packets", parts[2])
Utils.propertySet("sys.ritosip.call.video.lost", parts[3])
Utils.propertySet("sys.ritosip.call.video.jitter", parts[4])
val codecs = call.videoCodecs().split(',')
if(codecs.size >= 2) {
val txCodec = codecs[0]
val rxCodec = codecs[1]
Utils.propertySet("sys.ritosip.call.video.txCodec", txCodec)
Utils.propertySet("sys.ritosip.call.video.rxCodec", rxCodec)
}
if(MainActivity.videoView != null) {
Utils.propertySet(
"sys.ritosip.call.video.format",
"" + MainActivity.videoView.get_frameFormat()
)
}
}
} else {
Utils.propertySet("sys.ritosip.call.status", "standby")
Utils.propertySet("sys.ritosip.call.number", "")
Utils.propertySet("sys.ritosip.call.duration", "")
Utils.propertySet("sys.ritosip.call.audio.txCodec", "")
Utils.propertySet("sys.ritosip.call.audio.rxCodec", "")
Utils.propertySet("sys.ritosip.call.video.txCodec", "")
Utils.propertySet("sys.ritosip.call.video.rxCodec", "")
}
} else {
Utils.propertySet("sys.ritosip.sip.address", "")
Utils.propertySet("sys.ritosip.sip.status", "unregistered")
}
val display1 = Utils.checkDisplayConnection(0)
val display2 = Utils.checkDisplayConnection(1)
var connectedCount = 0
if(display1 == "connected")
connectedCount++
if(display2 == "connected")
connectedCount++
if(connectedDisplayCount != connectedCount) {
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
val displays = displayManager.displays
if(connectedCount == displays.size) {
connectedDisplayCount = connectedCount
sendActivityAction("update display")
}
}
//connectedDisplayCount = connectedCount
Utils.propertySet("sys.ritosip.display1.status", display1)
Utils.propertySet("sys.ritosip.display2.status", display2)
val camera1 = Utils.checkCameraConnection(0)
val camera2 = Utils.checkCameraConnection(1)
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(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.setVideoSource(true)
}
}
}
}
if(BaresipService.cameraFront) {
Utils.propertySet("sys.ritosip.media.input", "camera")
} else {
Utils.propertySet("sys.ritosip.media.input", "content")
}
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")
}
} else {
Utils.propertySet("sys.ritosip.usb.microphone", "disconnected")
}
delay(100) // 1초마다 실행
val now = LocalDateTime.now()
if(now.minute != updateDate.minute) {
sendActivityAction("update info")
updateDate = now
}
}
var nearFilePath = "/mnt/obb/near_$captureCount.jpg"
if(!Utils.copyFile("/mnt/obb/camera_near.jpg", nearFilePath)) {
nearFilePath = ""
}
var farFilePath = "/mnt/obb/far_$captureCount.jpg"
if(!Utils.copyFile("/mnt/obb/camera_far.jpg", farFilePath)) {
farFilePath = ""
}
Utils.deleteFile(File("/mnt/obb/camera_near.rgb565"))
Utils.deleteFile(File("/mnt/obb/camera_near.rgb565.done"))
Utils.deleteFile(File("/mnt/obb/camera_near.jpg"))
Utils.deleteFile(File("/mnt/obb/camera_far.rgb565"))
Utils.deleteFile(File("/mnt/obb/camera_far.rgb565.done"))
Utils.deleteFile(File("/mnt/obb/camera_far.jpg"))
for(i in captureCount - 10 .. captureCount - 5) {
var delFilePath = "/mnt/obb/near_$i.jpg"
Utils.deleteFile(File(delFilePath))
delFilePath = "/mnt/obb/far_$i.jpg"
Utils.deleteFile(File(delFilePath))
}
if(uas.size > 0) {
val ua = uas[0]
val call = ua.currentCall()
if (call != null) {
if (nearFilePath.length > 0)
Utils.propertySet("sys.ritosip.near_screen_file", nearFilePath)
if (farFilePath.length > 0)
Utils.propertySet("sys.ritosip.far_screen_file", farFilePath)
} else {
Utils.propertySet("sys.ritosip.near_screen_file", "")
Utils.propertySet("sys.ritosip.far_screen_file", "")
}
}
captureCount++
delay(100)
/*
delay(800) // 1초마다 실행
File("/mnt/obb/camera_near.rgb565").delete()
File("/mnt/obb/camera_near.rgb565.done").delete()
File("/mnt/obb/camera_near.jpg").delete()
File("/mnt/obb/camera_far.rgb565").delete()
File("/mnt/obb/camera_far.rgb565.done").delete()
File("/mnt/obb/camera_far.jpg").delete()
delay(200) // 1초마다 실행
*/
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action: String
if (intent == null) {
action = "Start"
Log.d(TAG, "Received onStartCommand with null intent")
} else {
// Utils.dumpIntent(intent)
action = intent.action!!
Log.d(TAG, "Received onStartCommand action $action")
}
when (action) {
"Start" -> {
isStartReceived = true
if (VERSION.SDK_INT < 31) {
@Suppress("DEPRECATION")
allNetworks = cm.allNetworks.toMutableSet()
}
updateDnsServers()
val assets = arrayOf("accounts", "config", "contacts")
var file = File(filesPath)
if (!file.exists()) {
Log.i(TAG, "Creating baresip directory")
try {
File(filesPath).mkdirs()
} catch (e: Error) {
Log.e(TAG, "Failed to create directory: $e")
}
}
for (a in assets) {
file = File("${filesPath}/$a")
if (!file.exists() && a != "config") {
Log.i(TAG, "Copying asset '$a'")
Utils.copyAssetToFile(applicationContext, a, "$filesPath/$a")
} else {
Log.d(TAG, "Asset '$a' already copied")
}
if (a == "config")
Config.initialize(applicationContext)
}
if (!File(filesDir, "tmp").exists())
File(filesDir, "tmp").mkdir()
if (!File(filesDir, "recordings").exists())
File(filesDir, "recordings").mkdir()
if (contactsMode != "android")
Contact.restoreBaresipContacts()
if (contactsMode != "baresip") {
Contact.loadAndroidContacts(applicationContext)
registerAndroidContactsObserver()
}
Contact.contactsUpdate()
val history = CallHistory.get()
if (history.isEmpty()) {
CallHistoryNew.restore()
} else {
for (old in history) {
val new = CallHistoryNew(old.aor, old.peerUri, old.direction)
new.stopTime = old.stopTime
new.startTime = old.startTime
new.recording = old.recording
callHistory.add(new)
}
CallHistoryNew.save()
}
Message.restore()
hotSpotAddresses = Utils.hotSpotAddresses()
linkAddresses = linkAddresses()
var addresses = ""
for (la in linkAddresses)
addresses = "$addresses;${la.key};${la.value}"
Log.i(TAG, "Link addresses: $addresses")
activeNetwork = cm.activeNetwork
Log.i(TAG, "Active network: $activeNetwork")
Log.d(TAG, "AEC/AGC/NS available = " +
"$aecAvailable/$agcAvailable/$nsAvailable")
val userAgent = Config.variable("user_agent")
Thread {
baresipStart(
filesPath,
addresses.removePrefix(";"),
logLevel,
if (userAgent != "")
userAgent
else
"ritosip v${BuildConfig.VERSION_NAME} " +
"(Android ${VERSION.RELEASE}/${System.getProperty("os.arch") ?: "?"})"
)
Api.log_level_set(0)
Log.logLevelSet(0)
Api.uag_enable_sip_trace(true)
}.start()
isServiceRunning = true
showStatusNotification()
sendActivityAction("update info")
val accounts = Utils.getFileContents("$filesPath/accounts")
if ((accounts != null) && accounts.isNotEmpty()) {
Utils.putFileContents("$filesPath/accounts",
accounts.toString(Charsets.UTF_8).replace(
"pubint=0;call_transfer",
"pubint=0;inreq_allowed=yes;call_transfer"
).toByteArray(Charsets.UTF_8)
)
}
if (linkAddresses.isEmpty()) {
val newIntent = Intent(this, MainActivity::class.java)
newIntent.flags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", "no network")
startActivity(newIntent)
}
applyTransportConfiguration()
}
"Start Content Observer" -> {
registerAndroidContactsObserver()
}
"Stop Content Observer" -> {
unRegisterAndroidContactsObserver()
}
"Call Answer" -> {
val uap = intent!!.getLongExtra("uap", 0L)
val callp = intent.getLongExtra("callp", 0L)
stopRinging()
stopMediaPlayer()
setCallVolume()
proximitySensing(true)
Api.ua_answer(uap, callp, Api.VIDMODE_ON)
}
"Call Reject" -> {
val callp = intent!!.getLongExtra("callp", 0L)
val call = Call.ofCallp(callp)
if (call == null) {
Log.w(TAG, "onStartCommand did not find call $callp")
} else {
val peerUri = call.peerUri
val aor = call.ua.account.aor
Log.d(TAG, "Aor $aor rejected incoming call $callp from $peerUri")
call.rejected = true
Api.ua_hangup(call.ua.uap, callp, 486, "Rejected")
}
}
"Transfer Deny" -> {
val callp = intent!!.getLongExtra("callp", 0L)
val call = Call.ofCallp(callp)
if (call == null)
Log.w(TAG, "onStartCommand did not find call $callp")
else
call.notifySipfrag(603, "Decline")
nm.cancel(TRANSFER_NOTIFICATION_ID)
}
"Message Save" -> {
val uap = intent!!.getLongExtra("uap", 0L)
val ua = UserAgent.ofUap(uap)
if (ua == null)
Log.w(TAG, "onStartCommand did not find UA $uap")
else
ChatsActivity.saveUaMessage(
ua.account.aor,
intent.getStringExtra("time")!!.toLong()
)
nm.cancel(MESSAGE_NOTIFICATION_ID)
}
"Message Delete" -> {
val uap = intent!!.getLongExtra("uap", 0L)
val ua = UserAgent.ofUap(uap)
if (ua == null)
Log.w(TAG, "onStartCommand did not find UA $uap")
else
ChatsActivity.deleteUaMessage(
ua.account.aor,
intent.getStringExtra("time")!!.toLong()
)
nm.cancel(MESSAGE_NOTIFICATION_ID)
}
"Update Notification" -> {
updateStatusNotification()
}
"Stop" -> {
cleanService()
if (isServiceRunning) {
baresipStop(false)
quitTimer.start()
}
}
else -> {
Log.e(TAG, "Unknown start action $action")
}
}
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy at Baresip Service")
cleanService()
if (isServiceRunning)
sendBroadcast(Intent("com.tutpro.baresip.plus.Restart"))
}
fun applyTransportConfiguration() {
Api.config_verify_server_set(false)
if(BaresipService.preferTransport == "udp") {
Api.config_transport_server_set("udp")
} else if(BaresipService.preferTransport == "tcp") {
Api.config_transport_server_set("tcp")
} else if(BaresipService.preferTransport == "tls") {
Api.config_transport_server_set("tls")
}
Api.uag_reset_transp(true, true)
}
fun sendActivityAction(order : String) {
val newIntent = Intent()
newIntent.setClassName(
"kr.co.rito.ritosip",
"com.tutpro.baresip.plus.MainActivity"
)
newIntent.flags =
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", order)
startActivity(newIntent)
}
@SuppressLint("DiscouragedApi")
@Keep
fun uaEvent(event: String, uap: Long, callp: Long) {
if (!isServiceRunning) return
val ev = event.split(",")
if (ev[0] == "create") {
val ua = UserAgent(uap)
ua.status = if (ua.account.regint == 0)
R.drawable.circle_white
else
R.drawable.circle_yellow
uas.add(ua)
val acc = ua.account
if (acc.authUser != "" && acc.authPass == NO_AUTH_PASS) {
val password = aorPasswords[acc.aor]
if (password != null) {
Api.account_set_auth_pass(acc.accp, password)
acc.authPass = password
} else {
Api.account_set_auth_pass(acc.accp, NO_AUTH_PASS)
}
}
Log.d(TAG, "got uaEvent $event/${acc.aor}")
return
}
if (ev[0] == "sndfile dump") {
Log.d(TAG, "Got sndfile dump ${ev[1]}")
if (Call.inCall()) {
if (ev[1].endsWith("enc.wav"))
Call.calls()[0].dumpfiles[0] = ev[1]
else
Call.calls()[0].dumpfiles[1] = ev[1]
}
return
}
if (ev[0] == "player sessionid") {
playerSessionId = ev[1].toInt()
Log.d(TAG, "got player sessionid $playerSessionId")
return
}
if (ev[0] == "recorder sessionid") {
recorderSessionId = ev[1].toInt()
Log.d(TAG, "got recorder sessionid $recorderSessionId")
return
}
val ua = UserAgent.ofUap(uap)
val aor = ua?.account?.aor
Log.d(TAG, "got uaEvent $event/$aor/$callp")
if (ua == null) {
when (ev[0]) {
"snapshot" -> {
val file = File(ev[2])
if (file.length() > 0) {
val fileName = ev[2].split("/").last()
try {
val bitmap = BitmapFactory.decodeStream(file.inputStream())
Utils.savePicture(this, bitmap, fileName)
Log.d(TAG, "Saved snapshot $fileName")
} catch (e: IOException) {
Log.d(TAG, "Failed to save snapshot $fileName")
}
}
file.delete()
}
else -> {
Log.w(TAG, "uaEvent did not find ua $uap")
}
}
return
}
val call = Call.ofCallp(callp)
if (call == null && callp != 0L &&
!setOf("incoming call", "call incoming", "call closed").contains(ev[0])) {
Log.w(TAG, "uaEvent $event did not find call $callp")
return
}
var newEvent: String? = null
for (accountIndex in uas.indices) {
if (uas[accountIndex].account.aor == aor) {
when (ev[0]) {
"registering", "unregistering" -> {
ua.status = R.drawable.circle_yellow
updateStatusNotification()
if (isMainVisible)
registrationUpdate.postValue(System.currentTimeMillis())
return
}
"registered" -> {
ua.status = if (Api.account_regint(ua.account.accp) == 0)
R.drawable.circle_white
else
R.drawable.circle_green
updateStatusNotification()
if (isMainVisible)
registrationUpdate.postValue(System.currentTimeMillis())
return
}
"registering failed" -> {
ua.status = if (Api.account_regint(ua.account.accp) == 0)
R.drawable.circle_white
else
R.drawable.circle_red
updateStatusNotification()
if (isMainVisible)
registrationUpdate.postValue(System.currentTimeMillis())
if (Utils.isVisible()) {
val reason = if (ev.size > 1) {
if (ev[1] == "Invalid argument") // Likely due to DNS lookup failure
": DNS lookup failed"
else
": ${ev[1]}"
} else
""
toast(String.format(getString(R.string.registering_failed), aor) + reason)
}
return
}
"call outgoing" -> {
if (call!!.status == "transferring")
break
stopMediaPlayer()
setCallVolume()
if (speakerPhone && !Utils.isSpeakerPhoneOn(am))
Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am)
proximitySensing(true)
return
}
"call ringing" -> {
playRingBack()
return
}
"call progress" -> {
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
stopMediaPlayer()
else
playRingBack()
return
}
"incoming call" -> {
val peerUri = ev[1]
val bevent = ev[2].toLong()
val toastMsg = if (!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO)))
getString(R.string.no_calls)
else if (!requestAudioFocus(applicationContext))
// request fails if there is an active telephony call
getString(R.string.audio_focus_denied)
else if (Call.inCall())
String.format(getString(R.string.call_auto_rejected),
Utils.friendlyUri(this, peerUri, ua.account))
else
""
if (toastMsg != "") {
Log.d(TAG, "Auto-rejecting incoming call $uap/$peerUri")
Api.sip_treply(callp, 486, "Busy Here")
Api.bevent_stop(bevent)
toast(toastMsg)
val name = "callwaiting_$toneCountry"
val resourceId = applicationContext.resources.getIdentifier(
name,
"raw",
applicationContext.packageName)
if (resourceId != 0) {
playUnInterrupted(resourceId, 1)
} else {
Log.e(TAG, "Callwaiting tone $name.wav not found\")")
}
if (ua.account.callHistory) {
CallHistoryNew.add(CallHistoryNew(aor, peerUri, "in"))
CallHistoryNew.save()
ua.account.missedCalls = true
}
return
}
// callp holds SIP message pointer
Api.ua_accept(uap, callp)
return
}
"call incoming" -> {
val peerUri = ev[1]
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
Call(callp, ua, peerUri, "in", "incoming", Utils.dtmfWatcher(callp)).add()
if (speakerPhone && !Utils.isSpeakerPhoneOn(am))
Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(this), am)
if (ua.account.answerMode == Api.ANSWERMODE_MANUAL) {
Log.d(TAG, "CurrentInterruptionFilter ${nm.currentInterruptionFilter}")
if (nm.currentInterruptionFilter <= NotificationManager.INTERRUPTION_FILTER_ALL)
startRinging()
} else {
val newIntent = Intent(this, MainActivity::class.java)
newIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.putExtra("action", "call answer")
newIntent.putExtra("callp", callp)
startActivity(newIntent)
return
}
if (!Utils.isVisible()) {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("action", "call show")
.putExtra("callp", callp)
val pi = PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this,
if (shouldVibrate()) MEDIUM_CHANNEL_ID else HIGH_CHANNEL_ID)
val caller = Utils.friendlyUri(this, peerUri, ua.account)
nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
.setCategory(Notification.CATEGORY_CALL)
.setAutoCancel(false)
.setOngoing(true)
.setContentTitle(getString(R.string.incoming_call_from))
.setContentText(caller)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setFullScreenIntent(pi, true)
val answerIntent = Intent(applicationContext, MainActivity::class.java)
answerIntent.putExtra("action", "call answer")
.putExtra("callp", callp)
val api = PendingIntent.getActivity(applicationContext, ANSWER_REQ_CODE, answerIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val rejectIntent = Intent(this, BaresipService::class.java)
rejectIntent.action = "Call Reject"
rejectIntent.putExtra("callp", callp)
val rpi = PendingIntent.getService(this, REJECT_REQ_CODE, rejectIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
nb.addAction(R.drawable.ic_stat_call,
getActionText(R.string.answer, R.color.colorGreen), api)
nb.addAction(R.drawable.ic_stat_call_end,
getActionText(R.string.reject, R.color.colorRed), rpi)
nm.notify(CALL_NOTIFICATION_ID, nb.build())
return
}
}
"remote call answered" -> {
newEvent = "call update"
}
"remote call offered" -> {
val callHasVideo = ev[1] == "1"
val remoteHasVideo = ev[2] == "1"
val ldir = ev[3].toInt()
val rdir = if (Utils.isCameraAvailable(this))
ev[4].toInt()
else
ev[4].toInt() and Api.SDP_RECVONLY
when (ev[5]) {
"0", "1" -> call!!.held = true // inactive, recvonly
"2", "3" -> call!!.held = false // sendonly, sendrecv
}
if (!isMainVisible || call!!.status != "connected")
return
if (!(callHasVideo && remoteHasVideo && ldir == 0) &&
(!callHasVideo && remoteHasVideo &&
(rdir != Api.SDP_INACTIVE) && (ldir != rdir))) {
postServiceEvent(ServiceEvent("call video request",
arrayListOf(uap, callp, rdir), System.nanoTime()))
return
}
newEvent = "call update"
}
"call answered" -> {
stopMediaPlayer()
if (call!!.status == "incoming")
call.status = "answered"
else
return
}
"call redirect", "video call redirect"-> {
stopMediaPlayer()
}
"call established" -> {
nm.cancel(CALL_NOTIFICATION_ID)
Log.d(TAG, "AoR $aor call $callp established in mode ${am.mode}")
if (am.mode != MODE_IN_COMMUNICATION)
am.mode = MODE_IN_COMMUNICATION
call!!.status = "connected"
if (recorderSessionId != 0) {
if (aecAvailable && !webrtcAec) {
aec = AcousticEchoCanceler.create(recorderSessionId)
if (aec != null) {
if (!aec!!.getEnabled()) {
aec!!.setEnabled(true)
if (aec!!.getEnabled())
Log.d(TAG, "AEC is enabled")
else
Log.w(TAG, "Failed to enable AEC")
}
}
else
Log.w(TAG, "Failed to create AEC")
}
if (agcAvailable) {
agc = AutomaticGainControl.create(recorderSessionId)
if (agc != null) {
if (!agc!!.getEnabled()) {
agc!!.setEnabled(true)
if (agc!!.getEnabled())
Log.d(TAG, "AGC is enabled")
}
}
else
Log.w(TAG, "Failed to create AGC")
}
if (nsAvailable) {
ns = NoiseSuppressor.create(recorderSessionId)
if (ns != null) {
if (!ns!!.getEnabled()) {
ns!!.setEnabled(true)
if (ns!!.getEnabled())
Log.d(TAG, "ns is enabled")
}
}
else
Log.w(TAG, "Failed to create NS")
}
recorderSessionId = 0
}
call.onhold = false
if (ua.account.callHistory)
call.startTime = GregorianCalendar()
if (!isMainVisible)
return
}
"call update" -> {
call!!.held = when (ev[1].toInt()) {
Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true
else /* Api.SDP_SENDONLY, Api.SDP_SENDRECV */ -> false
}
if (call.state() == Api.CALL_STATE_EARLY) {
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
stopMediaPlayer()
else
playRingBack()
}
if (!isMainVisible || call.status != "connected")
return
}
"call verified", "call secure" -> {
if (ev[0] == "call secure") {
call!!.security = R.drawable.locked_yellow
} else {
call!!.security = R.drawable.locked_green
call.zid = ev[1]
}
if (!isMainVisible)
return
}
"call transfer" -> {
if (!Utils.isVisible()) {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("action", "transfer show")
.putExtra("callp", callp).putExtra("uri", ev[1])
val pi = PendingIntent.getActivity(applicationContext, TRANSFER_REQ_CODE, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val target = Utils.friendlyUri(this, ev[1], ua.account)
nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
.setDefaults(Notification.DEFAULT_SOUND)
.setAutoCancel(true)
.setContentTitle(getString(R.string.transfer_request_to))
.setContentText(target)
val acceptIntent = Intent(applicationContext, MainActivity::class.java)
acceptIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
acceptIntent.putExtra("action","transfer accept")
.putExtra("callp", callp).putExtra("uri", ev[1])
val api = PendingIntent.getActivity(applicationContext, ACCEPT_REQ_CODE,
acceptIntent, PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT)
val denyIntent = Intent(this, BaresipService::class.java)
denyIntent.action = "Transfer Deny"
denyIntent.putExtra("callp", callp)
val dpi = PendingIntent.getService(this, DENY_REQ_CODE, denyIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
nb.addAction(R.drawable.ic_stat_call, getString(R.string.accept), api)
nb.addAction(R.drawable.ic_stat_call_end, getString(R.string.deny), dpi)
nm.notify(TRANSFER_NOTIFICATION_ID, nb.build())
return
}
}
"transfer failed" -> {
Log.d(TAG, "AoR $aor call $callp transfer failed: ${ev[1]}")
stopMediaPlayer()
call!!.referTo = ""
if (Utils.isVisible())
toast("${getString(R.string.transfer_failed)}: ${ev[1].trim()}")
if (!isMainVisible)
return
}
"call closed" -> {
Log.d(TAG, "AoR $aor call $callp is closed")
if (call != null) {
nm.cancel(CALL_NOTIFICATION_ID)
stopRinging()
stopMediaPlayer()
aec?.release()
agc?.release()
ns?.release()
val newCall = call.newCall
if (newCall != null) {
newCall.onHoldCall = null
call.newCall = null
}
val onHoldCall = call.onHoldCall
if (onHoldCall != null) {
onHoldCall.newCall = null
onHoldCall.referTo = ""
call.onHoldCall = null
}
call.remove()
val reason = ev[1]
val tone = ev[2]
if (tone == "busy") {
playBusy()
} else if (!Call.inCall()) {
resetCallVolume()
abandonAudioFocus(applicationContext)
proximitySensing(false)
}
if (call.dir == "out")
call.rejected = call.startTime == null &&
!reason.startsWith("408") &&
!reason.startsWith("480") &&
!reason.startsWith("Connection reset by")
val missed = call.dir == "in" && call.startTime == null && !call.rejected
if (ua.account.callHistory) {
val history = CallHistoryNew(aor, call.peerUri, call.dir)
history.startTime = call.startTime
history.stopTime = GregorianCalendar()
history.rejected = call.rejected
if (call.startTime != null && call.dumpfiles[0] != "")
history.recording = call.dumpfiles
CallHistoryNew.add(history)
CallHistoryNew.save()
ua.account.missedCalls = ua.account.missedCalls || missed
}
if (!Utils.isVisible()) {
if (missed) {
val caller = Utils.friendlyUri(this, call.peerUri, ua.account)
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("action", "call missed")
.putExtra("uap", uap)
val pi = PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
nb.setSmallIcon(R.drawable.ic_stat_phone_missed)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
.setCategory(Notification.CATEGORY_CALL)
.setAutoCancel(true)
var missedCalls = 0
for (notification in nm.activeNotifications)
if (notification.id == CALL_MISSED_NOTIFICATION_ID)
missedCalls++
if (missedCalls == 0) {
nb.setContentTitle(getString(R.string.missed_call_from))
nb.setContentText(caller)
} else {
nb.setContentTitle(getString(R.string.missed_calls))
nb.setContentText(
String.format(getString(R.string.missed_calls_count),
missedCalls + 1))
}
nm.notify(CALL_MISSED_NOTIFICATION_ID, nb.build())
}
return
}
}
val reason = ev[1].trim()
if ((reason != "") && (ua.calls().isEmpty())) {
if (reason[0].isDigit()) {
if (reason[0] != '3') {
toast("${getString(R.string.call_failed)}: $reason")
Utils.propertySet("sys.ritosip.call.event.result", "call_failed")
// Utils.propertySet("sys.ritosip.call.event.time", (Clock.systemUTC().millis() / 1000).toString())
// val responseIntent = Intent("kr.co.rito.ritosip.RESPONSE")
// responseIntent.putExtra("req", "call_result")
// responseIntent.putExtra("data", "failed")
// responseIntent.setPackage("kr.co.rito.sipsvc")
// sendBroadcast(responseIntent)
}
} else {
//toast("${getString(R.string.call_closed)}: ${Api.call_peer_uri(callp)}: $reason")
toast("${getString(R.string.call_closed)}")
}
}
}
}
}
}
postServiceEvent(ServiceEvent(newEvent ?: event, arrayListOf(uap, callp), System.nanoTime()))
}
private fun postServiceEvent(event: ServiceEvent) {
serviceEvents.add(event)
if (serviceEvents.size == 1) {
Log.d(TAG, "Posted service event ${event.event} at ${event.timeStamp}")
serviceEvent.postValue(Event(event.timeStamp))
} else {
Log.d(TAG, "Added service event ${event.event}")
}
}
@Keep
fun messageEvent(uap: Long, peerUri: String, cType: String, msg: ByteArray) {
val ua = UserAgent.ofUap(uap)
if (ua == null) {
Log.w(TAG, "messageEvent did not find ua $uap")
return
}
val charsetString = Utils.paramValue(cType.replace(" ", ""), "charset")
val charset = try {
Charset.forName(charsetString)
} catch (e: Exception) {
StandardCharsets.UTF_8
}
val text = try {
String(msg, charset)
} catch (e: Exception) {
val error = "Decoding of message failed using charset $charset from $cType!"
Log.w(TAG, error)
error
}
val timeStamp = System.currentTimeMillis()
val timeStampString = timeStamp.toString()
Log.d(TAG, "Message event for $uap from $peerUri at $timeStampString")
Message(ua.account.aor, peerUri, text, timeStamp, MESSAGE_DOWN, 0, "", true).add()
Message.save()
ua.account.unreadMessages = true
if (!Utils.isVisible()) {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("action", "message show").putExtra("uap", uap)
.putExtra("peer", peerUri)
val pi = PendingIntent.getActivity(applicationContext, MESSAGE_REQ_CODE, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val sender = Utils.friendlyUri(this, peerUri, ua.account)
nb.setSmallIcon(R.drawable.ic_stat_message)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.setAutoCancel(true)
.setContentTitle(getString(R.string.message_from) + " " + sender)
.setContentText(text)
val replyIntent = Intent(applicationContext, MainActivity::class.java)
replyIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_NEW_TASK
replyIntent.putExtra("action", "message reply")
.putExtra("uap", uap).putExtra("peer", peerUri)
val rpi = PendingIntent.getActivity(applicationContext, REPLY_REQ_CODE, replyIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val saveIntent = Intent(this, BaresipService::class.java)
saveIntent.action = "Message Save"
saveIntent.putExtra("uap", uap)
.putExtra("time", timeStampString)
val spi = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val deleteIntent = Intent(this, BaresipService::class.java)
deleteIntent.action = "Message Delete"
deleteIntent.putExtra("uap", uap)
.putExtra("time", timeStampString)
val dpi = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
nb.addAction(R.drawable.ic_stat_reply, "Reply", rpi)
nb.addAction(R.drawable.ic_stat_save, "Save", spi)
nb.addAction(R.drawable.ic_stat_delete, "Delete", dpi)
nm.notify(MESSAGE_NOTIFICATION_ID, nb.build())
return
}
if (nm.currentInterruptionFilter <= NotificationManager.INTERRUPTION_FILTER_ALL)
nt.play()
postServiceEvent(ServiceEvent("message show", arrayListOf(uap, peerUri), System.nanoTime()))
}
@Keep
fun started() {
Log.d(TAG, "Received 'started' from baresip")
Api.net_debug()
postServiceEvent(ServiceEvent("started", arrayListOf(callActionUri), System.nanoTime()))
callActionUri = ""
Log.d(TAG, "Battery optimizations are ignored: " +
"${pm.isIgnoringBatteryOptimizations(packageName)}")
Log.d(TAG, "Partial wake lock/wifi lock is held: " +
"${partialWakeLock.isHeld}/${wifiLock.isHeld}")
updateStatusNotification()
}
@Keep
@Suppress("UNUSED")
fun messageResponse(responseCode: Int, responseReason: String, time: String) {
Log.d(TAG, "Message response '$responseCode $responseReason' at $time")
val timeStamp = time.toLong()
for (m in messages.reversed())
if (m.timeStamp == timeStamp) {
if (responseCode < 300) {
m.direction = MESSAGE_UP
} else {
m.direction = MESSAGE_UP_FAIL
m.responseCode = responseCode
m.responseReason = responseReason
}
messageUpdate.postValue(System.currentTimeMillis())
break
} else {
if (m.timeStamp < timeStamp - 60000)
break
}
}
/*
@Keep
fun checkVideo(uap: String, callp: String, reqDir: Int): Int {
if (!isServiceRunning) return -1
val ua = UserAgent.ofUap(uap)
if (ua == null) {
Log.w(LOG_TAG, "checkVideo did not find ua $uap")
return -1
}
val call = Call.ofCallp(callp)
if (call == null) {
Log.d(LOG_TAG, "checkVideo did not find call $callp")
return -1
}
Log.d(LOG_TAG, "checkVideo reqDir = $reqDir, videoAllowed = ${call.videoAllowed}," +
" call.video = ${call.video}")
if ((reqDir == Api.SDP_INACTIVE) || (!cameraAvailable && (reqDir == Api.SDP_SENDONLY))) {
call.video = Api.SDP_INACTIVE
return Api.SDP_INACTIVE
}
if ((call.video == Api.SDP_INACTIVE) && !call.videoAllowed) {
serviceEvent.postValue(Event(ServiceEvent("call video request", arrayListOf(uap, peer),
timeStamp)))
return Api.SDP_INACTIVE
}
call.videoAllowed = false
if (cameraAvailable)
call.video = reqDir
else
call.video = Api.SDP_RECVONLY
return call.video
}*/
@Keep
fun stopped(error: String) {
Log.d(TAG, "Received 'stopped' from baresip with start error '$error'")
quitTimer.cancel()
cleanService()
isServiceRunning = false
postServiceEvent(ServiceEvent("stopped", arrayListOf(error), System.nanoTime()))
stopSelf()
}
private fun createNotificationChannels() {
val defaultChannel = NotificationChannel(DEFAULT_CHANNEL_ID, "Default",
NotificationManager.IMPORTANCE_LOW)
defaultChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
nm.createNotificationChannel(defaultChannel)
val highChannel = NotificationChannel(HIGH_CHANNEL_ID, "High",
NotificationManager.IMPORTANCE_HIGH)
highChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
highChannel.enableVibration(true)
nm.createNotificationChannel(highChannel)
val mediumChannel = NotificationChannel(MEDIUM_CHANNEL_ID, "Medium",
NotificationManager.IMPORTANCE_HIGH)
highChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
highChannel.enableVibration(false)
nm.createNotificationChannel(mediumChannel)
}
private fun showStatusNotification() {
val intent = Intent(applicationContext, MainActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
val pi = PendingIntent.getActivity(applicationContext, STATUS_REQ_CODE, intent,
PendingIntent.FLAG_IMMUTABLE)
val notificationLayout = RemoteViews(packageName, R.layout.status_notification)
snb.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat)
.setContentIntent(pi)
.setOngoing(true)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
if (VERSION.SDK_INT >= 31)
snb.foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_DEFAULT
try {
if (VERSION.SDK_INT >= 29)
startForeground(
STATUS_NOTIFICATION_ID, snb.build(),
if (VERSION.SDK_INT >= 30)
FOREGROUND_SERVICE_TYPE_PHONE_CALL or FOREGROUND_SERVICE_TYPE_MICROPHONE or
FOREGROUND_SERVICE_TYPE_CAMERA
else
FOREGROUND_SERVICE_TYPE_PHONE_CALL
)
else
startForeground(STATUS_NOTIFICATION_ID, snb.build())
} catch (e: Exception) {
Log.e(TAG, "Failed to start foreground service")
}
}
private fun updateStatusNotification() {
val notificationLayout = RemoteViews(packageName, R.layout.status_notification)
for (i: Int in 0..3) {
val resId = when (i) {
0-> R.id.status0
1-> R.id.status1
2-> R.id.status2
else -> R.id.status3
}
if (i < uas.size) {
notificationLayout.setImageViewResource(resId, uas[i].status)
notificationLayout.setViewVisibility(resId, View.VISIBLE)
} else {
notificationLayout.setViewVisibility(resId, View.INVISIBLE)
}
}
if (uas.size > 4)
notificationLayout.setViewVisibility(R.id.etc, View.VISIBLE)
else
notificationLayout.setViewVisibility(R.id.etc, View.INVISIBLE)
snb.setCustomContentView(notificationLayout)
// Don't know why, but without the delay the notification is not always updated
Timer().schedule(250) {
nm.notify(STATUS_NOTIFICATION_ID, snb.build())
}
}
private fun toast(message: String) {
// Handler(Looper.getMainLooper()).post {
// Toast.makeText(this@BaresipService.applicationContext, message, Toast.LENGTH_LONG).show()
// }
Handler(Looper.getMainLooper()).post {
showCustomToast(this@BaresipService.applicationContext, message)
}
}
fun showCustomToast(context: Context, message: String) {
val inflater = LayoutInflater.from(context)
val layout: View = inflater.inflate(R.layout.custom_toast, null)
val textView: TextView = layout.findViewById(R.id.toast_text)
textView.text = message
val toast = Toast(context)
toast.duration = Toast.LENGTH_LONG
toast.view = layout
toast.show()
}
private fun getActionText(@StringRes stringRes: Int, @ColorRes colorRes: Int): Spannable {
val spannable: Spannable = SpannableString(applicationContext.getText(stringRes))
spannable.setSpan(
ForegroundColorSpan(applicationContext.getColor(colorRes)), 0, spannable.length, 0)
return spannable
}
private fun startRinging() {
rt.isLooping = true
rt.play()
if (shouldVibrate()) {
vbTimer = Timer()
vbTimer!!.schedule(object : TimerTask() {
override fun run() {
vibrator.vibrate(
VibrationEffect.createOneShot(
500,
VibrationEffect.DEFAULT_AMPLITUDE
)
)
}
}, 500L, 2000L)
}
}
private fun shouldVibrate(): Boolean {
return if (am.ringerMode != RINGER_MODE_SILENT) {
if (am.ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
true
} else {
if (am.getStreamVolume(AudioManager.STREAM_RING) != 0) {
@Suppress("DEPRECATION")
Settings.System.getInt(contentResolver, Settings.System.VIBRATE_WHEN_RINGING, 0) == 1
} else {
false
}
}
} else {
false
}
}
private fun stopRinging() {
rt.stop()
if (vbTimer != null) {
vbTimer!!.cancel()
vbTimer = null
}
}
@SuppressLint("DiscouragedApi")
private fun playRingBack() {
if (mediaPlayer == null) {
val name = "ringback_$toneCountry"
val resourceId = applicationContext.resources.getIdentifier(
name,
"raw",
applicationContext.packageName)
if (resourceId != 0) {
mediaPlayer = MediaPlayer.create(this, resourceId)
mediaPlayer?.isLooping = true
mediaPlayer?.start()
} else {
Log.e(TAG, "Ringback tone $name.wav not found")
}
}
}
@SuppressLint("DiscouragedApi")
private fun playBusy() {
if (mediaPlayer == null ) {
val name = "busy_$toneCountry"
val resourceId = applicationContext.resources.getIdentifier(
name,
"raw",
applicationContext.packageName)
if (resourceId != 0) {
mediaPlayer = MediaPlayer.create(this, resourceId)
mediaPlayer?.setOnCompletionListener {
stopMediaPlayer()
if (!Call.inCall()) {
resetCallVolume()
abandonAudioFocus(applicationContext)
proximitySensing(false)
}
}
mediaPlayer?.start()
} else {
Log.e(TAG, "Busy tone $name.wav not found")
}
}
}
private fun playUnInterrupted(raw: Int, count: Int) {
val player = MediaPlayer.create(this, raw)
player.setOnCompletionListener {
it.stop()
it.release()
if (count > 1) playUnInterrupted(raw, count - 1)
}
player.start()
}
private fun stopMediaPlayer() {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayer = null
}
private fun setCallVolume() {
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)
Log.d(TAG, "Orig/new/max $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
}
}
private fun resetCallVolume() {
if (callVolume != 0)
for ((streamType, streamVolume) in origVolume) {
am.setStreamVolume(streamType, streamVolume, 0)
Log.d(TAG, "Reset $streamType volume to ${am.getStreamVolume(streamType)}")
}
}
@SuppressLint("WakelockTimeout", "Wakelock")
private fun proximitySensing(enable: Boolean) {
if (enable) {
if (!proximityWakeLock.isHeld) {
Log.d(TAG, "Acquiring proximity wake lock")
proximityWakeLock.acquire()
} else {
Log.d(TAG, "Proximity wake lock already acquired")
}
} else {
if (proximityWakeLock.isHeld) {
proximityWakeLock.release()
Log.d(TAG, "Released proximity wake lock")
} else {
Log.d(TAG, "Proximity wake lock is not held")
}
}
}
private fun updateNetwork() {
/* for (n in allNetworks)
Log.d(TAG, "NETWORK $n with caps ${cm.getNetworkCapabilities(n)} and props " +
"${cm.getLinkProperties(n)} is active ${isNetworkActive(n)}") */
updateDnsServers()
val addresses = linkAddresses()
Log.d(TAG, "Old/new link addresses $linkAddresses/$addresses")
var added = 0
for (a in addresses)
if (!linkAddresses.containsKey(a.key)) {
if (Api.net_add_address_ifname(a.key, a.value) != 0)
Log.e(TAG, "Failed to add address: $a")
else
added++
}
var removed = 0
for (a in linkAddresses)
if (!addresses.containsKey(a.key)) {
if (Api.net_rm_address(a.key) != 0)
Log.e(TAG, "Failed to remove address: $a")
else
removed++
}
val active = cm.activeNetwork
Log.d(TAG, "Added/Removed/Old/New Active = $added/$removed/$activeNetwork/$active")
if (added > 0 || removed > 0 || active != activeNetwork) {
linkAddresses = addresses
activeNetwork = active
Api.uag_reset_transp(register = true, reinvite = true)
}
Api.net_debug()
if (activeNetwork != null) {
val caps = cm.getNetworkCapabilities(activeNetwork)
if (caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
Log.d(TAG, "Acquiring WiFi Lock")
wifiLock.acquire()
return
}
}
Log.d(TAG, "Releasing WiFi Lock")
wifiLock.release()
}
private fun linkAddresses(): MutableMap<String, String> {
val addresses = mutableMapOf<String, String>()
for (n in allNetworks) {
val caps = cm.getNetworkCapabilities(n) ?: continue
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) {
val props = cm.getLinkProperties(n) ?: continue
for (la in props.linkAddresses)
if (la.scope == android.system.OsConstants.RT_SCOPE_UNIVERSE &&
props.interfaceName != null && la.address.hostAddress != null &&
afMatch(la.address.hostAddress!!))
addresses[la.address.hostAddress!!] = props.interfaceName!!
}
}
if (hotSpotIsEnabled) {
for ((k, v) in hotSpotAddresses)
if (afMatch(k))
addresses[k] = v
}
return addresses
}
private fun afMatch(address: String): Boolean {
return when (addressFamily) {
"" -> true
"ipv4" -> address.contains(".")
else -> address.contains(":")
}
}
private fun updateDnsServers() {
if (isServiceRunning && !dynDns)
return
val servers = mutableListOf<InetAddress>()
// Use DNS servers first from active network (if available)
if (cm.activeNetwork != null) {
val linkProps = cm.getLinkProperties(cm.activeNetwork)
if (linkProps != null)
servers.addAll(linkProps.dnsServers)
}
// Then add DNS servers from the other networks
for (n in allNetworks) {
if (n == cm.activeNetwork) continue
val linkProps = cm.getLinkProperties(n)
if (linkProps != null)
for (server in linkProps.dnsServers)
if (!servers.contains(server)) servers.add(server)
}
// Update if change
if (servers != dnsServers) {
if (isServiceRunning && Config.updateDnsServers(servers) != 0) {
Log.w(TAG, "Failed to update DNS servers '${servers}'")
} else {
// Log.d(TAG, "Updated DNS servers: '${servers}'")
dnsServers = servers
}
}
}
private fun registerAndroidContactsObserver() {
if (!androidContactsObserverRegistered)
try {
contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, androidContactsObserver)
androidContactsObserverRegistered = true
} catch (e: SecurityException) {
Log.i(TAG, "No Contacts permission")
}
}
private fun unRegisterAndroidContactsObserver() {
if (androidContactsObserverRegistered) {
contentResolver.unregisterContentObserver(androidContactsObserver)
androidContactsObserverRegistered = false
}
}
private fun cleanService() {
if (!isServiceClean) {
this.unregisterReceiver(bluetoothReceiver)
this.unregisterReceiver(hotSpotReceiver)
stopRinging()
stopMediaPlayer()
abandonAudioFocus(applicationContext)
uas.clear()
callHistory.clear()
messages.clear()
if (this::nm.isInitialized)
nm.cancelAll()
if (this::partialWakeLock.isInitialized && partialWakeLock.isHeld)
partialWakeLock.release()
if (this::proximityWakeLock.isInitialized && proximityWakeLock.isHeld)
proximityWakeLock.release()
if (this::wifiLock.isInitialized)
wifiLock.release()
if (this::androidContactsObserver.isInitialized)
contentResolver.unregisterContentObserver(androidContactsObserver)
isServiceClean = true
}
}
private external fun baresipStart(
path: String,
addresses: String,
logLevel: Int,
software: String
)
external fun baresipStop(force: Boolean)
companion object {
var isServiceRunning = false
var isStartReceived = false
var isConfigInitialized = false
var libraryLoaded = false
var supportedCameras = false
var cameraFront = true
var callVolume = 0
var speakerPhone = false
var audioDelay = if (VERSION.SDK_INT < 31) 1500L else 500L
var dynDns = false
var filesPath = ""
var pName = ""
var logLevel = 2
var sipTrace = false
var callActionUri = ""
var isMainVisible = false
var isMicMuted = false
var isVideoMuted = false
var isRecOn = false
var toneCountry = "us"
var videoSize = Size(0, 0)
val uas = ArrayList<UserAgent>()
val calls = ArrayList<Call>()
var callHistory = ArrayList<CallHistoryNew>()
var messages = ArrayList<Message>()
val messageUpdate = MutableLiveData<Long>()
val contactUpdate = MutableLiveData<Long>()
val registrationUpdate = MutableLiveData<Long>()
val baresipContacts = ArrayList<Contact.BaresipContact>()
val androidContacts = ArrayList<Contact.AndroidContact>()
val contacts = ArrayList<Contact>()
val contactNames = ArrayList<String>()
var contactsMode = "baresip"
val chatTexts: MutableMap<String, String> = mutableMapOf()
val activities = mutableListOf<String>()
var addressFamily = ""
var dnsServers = listOf<InetAddress>()
val serviceEvent = MutableLiveData<Event<Long>>()
val serviceEvents = mutableListOf<ServiceEvent>()
// <aor, password> of those accounts that have auth username without auth password
val aorPasswords = mutableMapOf<String, String>()
var audioFocusRequest: AudioFocusRequestCompat? = null
var aecAvailable = AcousticEchoCanceler.isAvailable()
var agcAvailable = AutomaticGainControl.isAvailable()
private val nsAvailable = NoiseSuppressor.isAvailable()
private var aec: AcousticEchoCanceler? = null
private var ns: NoiseSuppressor? = null
private var agc: AutomaticGainControl? = null
var webrtcAec = false
private var btAdapter: BluetoothAdapter? = null
private var playerSessionId = 0
private var recorderSessionId = 0
/* Added by ritoseo */
val DISPLAY_SPLIT_MODE_원거리_최대_근거리_우하단 = 1
val DISPLAY_SPLIT_MODE_원거리_최대_근거리_우상단 = 2
val DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌상단 = 3
val DISPLAY_SPLIT_MODE_원거리_최대_근거리_좌하단 = 4
val DISPLAY_SPLIT_MODE_원거리_대상단_근거리_소하단 = 5
val DISPLAY_SPLIT_MODE_원거리_대좌단_근거리_소우단 = 6
val DISPLAY_SPLIT_MODE_원거리_대하단_근거리_소상단 = 7
val DISPLAY_SPLIT_MODE_원거리_반좌단_근거리_반우단 = 8
var isSupportAudioCall = false
var deviceIpAddress = "0.0.0.0"
var deviceName = ""
var preferTransport = "udp"
var videoFormatHeight = 480
var connectedDisplayCount = 0
var callAnswerMode = "auto"
var audioInputDevice = "usb"
var useDisplaySplitMode = false
var totalDurationSeconds = 0
var displaySplitMode = DISPLAY_SPLIT_MODE_원거리_최대_근거리_우하단
var farViewDisplayId = 1
var nearViewDisplayId = 2
var farViewLayout : Rect = Rect(0, 0, 1920, 1080)
var nearViewLayout : Rect = Rect(0, 0, 1920, 1080)
fun requestAudioFocus(ctx: Context): Boolean {
Log.d(TAG, "Requesting audio focus")
if (audioFocusRequest != null) {
Log.d(TAG, "Already focused")
return true
}
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val attributes = AudioAttributesCompat.Builder()
.setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
.build()
audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(attributes)
.setOnAudioFocusChangeListener { }
.build()
if (AudioManagerCompat.requestAudioFocus(am, audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "requestAudioFocus granted")
if (isBluetoothHeadsetConnected(ctx))
startBluetoothSco(ctx, 250L, 3)
} else {
Log.w(TAG, "requestAudioFocus denied")
audioFocusRequest = null
}
return audioFocusRequest != null
}
fun abandonAudioFocus(ctx: Context) {
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (audioFocusRequest != null) {
Log.d(TAG, "Abandoning audio focus")
if (AudioManagerCompat.abandonAudioFocusRequest(am, audioFocusRequest!!) ==
AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
audioFocusRequest = null
if (isBluetoothHeadsetConnected(ctx))
stopBluetoothSco(ctx)
} else {
Log.e(TAG, "Failed to abandon audio focus")
}
}
am.mode = MODE_NORMAL
}
private fun isBluetoothHeadsetConnected(ctx: Context): Boolean {
if (VERSION.SDK_INT >= 31 &&
ActivityCompat.checkSelfPermission(ctx, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_DENIED)
return false
return btAdapter != null && btAdapter!!.isEnabled &&
btAdapter!!.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothAdapter.STATE_CONNECTED
}
private fun isBluetoothScoOn(am: AudioManager): Boolean {
return if (VERSION.SDK_INT < 31)
@Suppress("DEPRECATION")
am.isBluetoothScoOn
else
if (am.communicationDevice != null)
am.communicationDevice!!.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
else
false
}
private fun startBluetoothSco(ctx: Context, delay: Long, count: Int) {
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (isBluetoothScoOn(am)) {
Log.d(TAG, "Bluetooth SCO is already on")
return
}
Log.d(TAG, "Starting Bluetooth SCO at count $count")
Handler(Looper.getMainLooper()).postDelayed({
if (VERSION.SDK_INT < 31) {
@Suppress("DEPRECATION")
am.startBluetoothSco()
} else {
Utils.setCommunicationDevice(am, AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
}
if (!isBluetoothScoOn(am) && count > 1)
startBluetoothSco(ctx, delay, count - 1)
else
am.isBluetoothScoOn = true
}, delay)
}
private fun stopBluetoothSco(ctx: Context) {
Log.d(TAG, "Stopping Bluetooth SCO")
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (!isBluetoothScoOn(am)) {
Log.d(TAG, "Bluetooth SCO is already off")
return
}
Handler(Looper.getMainLooper()).postDelayed({
if (VERSION.SDK_INT < 31)
@Suppress("DEPRECATION")
am.stopBluetoothSco()
else
am.clearCommunicationDevice()
am.isBluetoothScoOn = false
}, 100)
}
}
}