2558 lines
115 KiB
Kotlin
2558 lines
115 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)
|
|
Utils.propertySet("sys.ritosip.ipv4.address", netInfo.get("ip")!!)
|
|
|
|
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"))
|
|
deviceIpAddress = netInfo.get("ip")!!
|
|
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 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 (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 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)
|
|
}
|
|
|
|
}
|
|
|
|
}
|