Mobile account and calling related fixes and enhancements
Search and show also contact URIs in suggestions
This commit is contained in:
@ -892,7 +892,8 @@ class BaresipService: Service() {
|
|||||||
Blocked.clear(ua.account.aor)
|
Blocked.clear(ua.account.aor)
|
||||||
BlockRule.clear(ua.account.aor)
|
BlockRule.clear(ua.account.aor)
|
||||||
Api.ua_destroy(uap)
|
Api.ua_destroy(uap)
|
||||||
if (isNativeReady) Account.saveAccounts()
|
ua.remove()
|
||||||
|
Account.saveAccounts()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -903,10 +904,11 @@ class BaresipService: Service() {
|
|||||||
R.drawable.circle_white
|
R.drawable.circle_white
|
||||||
else
|
else
|
||||||
circleRed.getValue(colorblind)
|
circleRed.getValue(colorblind)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
R.drawable.circle_white
|
R.drawable.circle_white
|
||||||
}
|
}
|
||||||
} else if (ua.account.regint == 0)
|
else if (ua.account.regint == 0)
|
||||||
R.drawable.circle_white
|
R.drawable.circle_white
|
||||||
else
|
else
|
||||||
circleYellow.getValue(colorblind)
|
circleYellow.getValue(colorblind)
|
||||||
@ -1076,7 +1078,7 @@ class BaresipService: Service() {
|
|||||||
"incoming call" -> {
|
"incoming call" -> {
|
||||||
if (speakerPhoneAuto)
|
if (speakerPhoneAuto)
|
||||||
speakerPhone = true
|
speakerPhone = true
|
||||||
val peerUri = ev[1]
|
val peerUri = Utils.uriUnescape(ev[1])
|
||||||
var blockedCall = false
|
var blockedCall = false
|
||||||
val toastMsg = if (Call.isAnyCallActive(this))
|
val toastMsg = if (Call.isAnyCallActive(this))
|
||||||
String.format(
|
String.format(
|
||||||
@ -1143,7 +1145,7 @@ class BaresipService: Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"call incoming" -> {
|
"call incoming" -> {
|
||||||
val peerUri = ev[1]
|
val peerUri = Utils.uriUnescape(ev[1])
|
||||||
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
|
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
|
||||||
if (Call.ofCallp(callp) == null)
|
if (Call.ofCallp(callp) == null)
|
||||||
Call(callp, ua, peerUri, "in", "incoming").add()
|
Call(callp, ua, peerUri, "in", "incoming").add()
|
||||||
@ -1714,7 +1716,9 @@ class BaresipService: Service() {
|
|||||||
|
|
||||||
private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null
|
private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null
|
||||||
|
|
||||||
fun runCall(uap: Long, uri: String, conferenceCall: Boolean, onHoldCallp: Long) {
|
fun runCall(uap: Long, uriText: String, conferenceCall: Boolean, onHoldCallp: Long) {
|
||||||
|
|
||||||
|
val uri = Utils.uriUnescape(uriText)
|
||||||
|
|
||||||
val ua = UserAgent.ofUap(uap)
|
val ua = UserAgent.ofUap(uap)
|
||||||
if (ua != null && ua.account.isMobile) {
|
if (ua != null && ua.account.isMobile) {
|
||||||
@ -3047,13 +3051,27 @@ class BaresipService: Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun registerPhoneAccount() {
|
private fun registerPhoneAccount() {
|
||||||
val phoneAccountHandle = getPhoneAccountHandle(this)
|
val sipHandle = getPhoneAccountHandle(this, SIP_ACCOUNT_ID)
|
||||||
val phoneAccount = android.telecom.PhoneAccount.builder(phoneAccountHandle, getString(R.string.app_name))
|
val pstnHandle = getPhoneAccountHandle(this, PSTN_ACCOUNT_ID)
|
||||||
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_SELF_MANAGED)
|
|
||||||
|
val sipAccount = android.telecom.PhoneAccount.builder(sipHandle, getString(R.string.app_name))
|
||||||
|
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_SELF_MANAGED or
|
||||||
|
android.telecom.PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
|
||||||
|
.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.mipmap.ic_launcher))
|
||||||
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)
|
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)
|
||||||
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)
|
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)
|
||||||
.build()
|
.build()
|
||||||
tm.registerPhoneAccount(phoneAccount)
|
|
||||||
|
val pstnAccount = android.telecom.PhoneAccount.builder(pstnHandle, getString(R.string.app_name) + " Mobile")
|
||||||
|
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_CALL_PROVIDER or
|
||||||
|
android.telecom.PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
|
||||||
|
.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.mipmap.ic_launcher))
|
||||||
|
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)
|
||||||
|
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
tm.registerPhoneAccount(sipAccount)
|
||||||
|
tm.registerPhoneAccount(pstnAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSimReady(): Boolean {
|
fun isSimReady(): Boolean {
|
||||||
@ -3197,7 +3215,7 @@ class BaresipService: Service() {
|
|||||||
var instance: BaresipService? = null
|
var instance: BaresipService? = null
|
||||||
var isServiceRunning = false
|
var isServiceRunning = false
|
||||||
var isNativeReady = false
|
var isNativeReady = false
|
||||||
var mobileAccount = true
|
var mobileAccount = false
|
||||||
var isStartReceived = false
|
var isStartReceived = false
|
||||||
var isConfigInitialized = false
|
var isConfigInitialized = false
|
||||||
var libraryLoaded = false
|
var libraryLoaded = false
|
||||||
@ -3295,11 +3313,12 @@ class BaresipService: Service() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
internal const val KEY_TEXT_REPLY = "key_text_reply_baresip"
|
internal const val KEY_TEXT_REPLY = "key_text_reply_baresip"
|
||||||
private const val PHONE_ACCOUNT_ID = "baresip_phone_account"
|
const val SIP_ACCOUNT_ID = "baresip_sip_account"
|
||||||
|
const val PSTN_ACCOUNT_ID = "baresip_phone_account"
|
||||||
|
|
||||||
fun getPhoneAccountHandle(ctx: Context): PhoneAccountHandle {
|
fun getPhoneAccountHandle(ctx: Context, id: String = SIP_ACCOUNT_ID): PhoneAccountHandle {
|
||||||
val componentName = android.content.ComponentName(ctx, ConnectionService::class.java)
|
val componentName = android.content.ComponentName(ctx, ConnectionService::class.java)
|
||||||
return PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID)
|
return PhoneAccountHandle(componentName, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMicMute(mute: Boolean) {
|
fun setMicMute(mute: Boolean) {
|
||||||
|
|||||||
@ -460,7 +460,8 @@ private fun NewChatPeer(navController: NavController, account: Account) {
|
|||||||
String.format(noTelephonyProviderText, account.aor)
|
String.format(noTelephonyProviderText, account.aor)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
""
|
""
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
Utils.telToSip(peerUri, account)
|
Utils.telToSip(peerUri, account)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -532,7 +533,7 @@ private fun NewChatPeer(navController: NavController, account: Account) {
|
|||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = filteredSuggestions,
|
items = filteredSuggestions,
|
||||||
key = { (contact, _, _) -> "${contact.id()}" }
|
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
|
||||||
) { (contact, annotatedName, matchingUri) ->
|
) { (contact, annotatedName, matchingUri) ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -551,15 +552,17 @@ private fun NewChatPeer(navController: NavController, account: Account) {
|
|||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
if (matchingUri != null) {
|
if (matchingUri != null) {
|
||||||
val tel = matchingUri.uri.substring(4)
|
val uriPart = matchingUri.uri.substringAfter(":")
|
||||||
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
|
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
|
||||||
tel,
|
newPeer.filter { c -> c.isDigit() || c == '+' }
|
||||||
newPeer.filter { c -> c.isDigit() || c == '+' })
|
else
|
||||||
|
newPeer
|
||||||
|
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
|
||||||
Text(
|
Text(
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
if (matchingUri.label.isNotEmpty())
|
if (matchingUri.label.isNotEmpty())
|
||||||
append("${matchingUri.label} ")
|
append("${matchingUri.label} ")
|
||||||
append(annotatedTel)
|
append(annotatedUri)
|
||||||
},
|
},
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@ -576,31 +579,32 @@ private fun NewChatPeer(navController: NavController, account: Account) {
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = newPeer,
|
value = newPeer,
|
||||||
placeholder = { Text(stringResource(R.string.new_chat_peer)) },
|
placeholder = { Text(stringResource(R.string.new_chat_peer)) },
|
||||||
onValueChange = {
|
onValueChange = { input ->
|
||||||
newPeer = it
|
newPeer = input
|
||||||
showSuggestions = newPeer.length > 1
|
showSuggestions = newPeer.length > 1
|
||||||
filteredSuggestions = if (it.isEmpty())
|
filteredSuggestions = if (input.length <= 1)
|
||||||
emptyList()
|
emptyList()
|
||||||
else {
|
else {
|
||||||
val normalizedInput = Utils.unaccent(it)
|
val normalizedInput = Utils.unaccent(input)
|
||||||
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
|
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
|
||||||
BaresipService.contacts.mapNotNull { contact ->
|
BaresipService.contacts.flatMap { contact ->
|
||||||
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
||||||
var matchingUri: Contact.ContactUri? = null
|
val uris = contact.uris()
|
||||||
if (numericInput.isNotEmpty()) {
|
val matchingUris = uris.filter { u ->
|
||||||
matchingUri = contact.uris().find { u ->
|
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
|
||||||
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
|
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
|
||||||
}
|
}
|
||||||
}
|
if (nameMatch) {
|
||||||
if (nameMatch || matchingUri != null) {
|
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
|
||||||
val annotatedName = if (nameMatch)
|
if (uris.isEmpty())
|
||||||
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
|
listOf(Triple(contact, annotatedName, null))
|
||||||
else
|
else
|
||||||
AnnotatedString(contact.name())
|
uris.map { Triple(contact, annotatedName, it) }
|
||||||
Triple(contact, annotatedName, matchingUri)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
else if (matchingUris.isNotEmpty())
|
||||||
|
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -643,9 +647,9 @@ private fun NewChatPeer(navController: NavController, account: Account) {
|
|||||||
else
|
else
|
||||||
String.format(contactNoSipOrTelUriText, peerText)
|
String.format(contactNoSipOrTelUriText, peerText)
|
||||||
showAlert.value = true
|
showAlert.value = true
|
||||||
} else {
|
|
||||||
makeChat(navController, account, peerText)
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
makeChat(navController, account, peerText)
|
||||||
}
|
}
|
||||||
else if (uris.size == 1)
|
else if (uris.size == 1)
|
||||||
makeChat(navController, account, uris[0].uri)
|
makeChat(navController, account, uris[0].uri)
|
||||||
|
|||||||
@ -44,14 +44,13 @@ class ConnectionService : ConnectionService() {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val currentRoute = it.callAudioState?.route ?: CallAudioState.ROUTE_EARPIECE
|
val currentRoute = it.callAudioState?.route ?: CallAudioState.ROUTE_EARPIECE
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
if (speaker) {
|
if (speaker)
|
||||||
it.setAudioRoute(CallAudioState.ROUTE_SPEAKER)
|
it.setAudioRoute(CallAudioState.ROUTE_SPEAKER)
|
||||||
} else if (currentRoute == CallAudioState.ROUTE_SPEAKER) {
|
else if (currentRoute == CallAudioState.ROUTE_SPEAKER)
|
||||||
it.setAudioRoute(CallAudioState.ROUTE_EARPIECE)
|
it.setAudioRoute(CallAudioState.ROUTE_EARPIECE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateIncomingConnection(
|
override fun onCreateIncomingConnection(
|
||||||
connectionManagerPhoneAccount: PhoneAccountHandle?,
|
connectionManagerPhoneAccount: PhoneAccountHandle?,
|
||||||
@ -72,6 +71,8 @@ class ConnectionService : ConnectionService() {
|
|||||||
Connection.CAPABILITY_HOLD or
|
Connection.CAPABILITY_HOLD or
|
||||||
Connection.CAPABILITY_MERGE_CONFERENCE or
|
Connection.CAPABILITY_MERGE_CONFERENCE or
|
||||||
Connection.CAPABILITY_SWAP_CONFERENCE
|
Connection.CAPABILITY_SWAP_CONFERENCE
|
||||||
|
if (request?.accountHandle?.id == BaresipService.SIP_ACCOUNT_ID)
|
||||||
|
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
|
||||||
connection.audioModeIsVoip = true
|
connection.audioModeIsVoip = true
|
||||||
|
|
||||||
val call = Call.ofCallp(callp)
|
val call = Call.ofCallp(callp)
|
||||||
@ -110,9 +111,14 @@ class ConnectionService : ConnectionService() {
|
|||||||
val rootExtras = request?.extras
|
val rootExtras = request?.extras
|
||||||
val nestedExtras = rootExtras?.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)
|
val nestedExtras = rootExtras?.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)
|
||||||
|
|
||||||
val uap = rootExtras?.getLong("uap", 0L).takeIf { it != 0L }
|
var uap = rootExtras?.getLong("uap", 0L).takeIf { it != 0L }
|
||||||
?: nestedExtras?.getLong("uap") ?: 0L
|
?: nestedExtras?.getLong("uap") ?: 0L
|
||||||
|
|
||||||
|
if (uap == 0L && BaresipService.uas.value.isNotEmpty()) {
|
||||||
|
uap = BaresipService.uas.value[0].uap
|
||||||
|
Log.d(TAG, "Outgoing connection request from system, using default uap $uap")
|
||||||
|
}
|
||||||
|
|
||||||
val conferenceCall = rootExtras?.getBoolean("conferenceCall", false) ?:
|
val conferenceCall = rootExtras?.getBoolean("conferenceCall", false) ?:
|
||||||
nestedExtras?.getBoolean("conferenceCall") ?: false
|
nestedExtras?.getBoolean("conferenceCall") ?: false
|
||||||
|
|
||||||
@ -122,7 +128,7 @@ class ConnectionService : ConnectionService() {
|
|||||||
val onHoldCallp = rootExtras?.getLong("onHoldCallp", 0L).takeIf { it != 0L }
|
val onHoldCallp = rootExtras?.getLong("onHoldCallp", 0L).takeIf { it != 0L }
|
||||||
?: nestedExtras?.getLong("onHoldCallp") ?: 0L
|
?: nestedExtras?.getLong("onHoldCallp") ?: 0L
|
||||||
|
|
||||||
val destination = request?.address?.encodedSchemeSpecificPart ?: ""
|
val destination = request?.address?.schemeSpecificPart ?: ""
|
||||||
|
|
||||||
Log.d(TAG, "onCreateOutgoingConnection to $destination (uap=$uap)")
|
Log.d(TAG, "onCreateOutgoingConnection to $destination (uap=$uap)")
|
||||||
|
|
||||||
@ -143,6 +149,8 @@ class ConnectionService : ConnectionService() {
|
|||||||
Connection.CAPABILITY_HOLD or
|
Connection.CAPABILITY_HOLD or
|
||||||
Connection.CAPABILITY_MERGE_CONFERENCE or
|
Connection.CAPABILITY_MERGE_CONFERENCE or
|
||||||
Connection.CAPABILITY_SWAP_CONFERENCE
|
Connection.CAPABILITY_SWAP_CONFERENCE
|
||||||
|
if (request?.accountHandle?.id == BaresipService.SIP_ACCOUNT_ID)
|
||||||
|
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
|
||||||
|
|
||||||
if (!pstnCall) {
|
if (!pstnCall) {
|
||||||
connection.audioModeIsVoip = true
|
connection.audioModeIsVoip = true
|
||||||
@ -225,9 +233,9 @@ class ConnectionService : ConnectionService() {
|
|||||||
if (callp != 0L) {
|
if (callp != 0L) {
|
||||||
Api.ua_hangup(uap, callp, 0, "")
|
Api.ua_hangup(uap, callp, 0, "")
|
||||||
connections.remove(callp)
|
connections.remove(callp)
|
||||||
} else {
|
|
||||||
pendingOutgoingConnection = null
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
pendingOutgoingConnection = null
|
||||||
setDisconnected(DisconnectCause(DisconnectCause.CANCELED))
|
setDisconnected(DisconnectCause(DisconnectCause.CANCELED))
|
||||||
destroy()
|
destroy()
|
||||||
}
|
}
|
||||||
@ -281,9 +289,9 @@ class ConnectionService : ConnectionService() {
|
|||||||
call.showOnHoldNotice.value = true
|
call.showOnHoldNotice.value = true
|
||||||
// 3. Tell Telecom the move is complete
|
// 3. Tell Telecom the move is complete
|
||||||
setOnHold()
|
setOnHold()
|
||||||
} else {
|
|
||||||
Log.e(TAG, "SIP Hold failed for $callp")
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
Log.e(TAG, "SIP Hold failed for $callp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,9 +307,9 @@ class ConnectionService : ConnectionService() {
|
|||||||
call.showOnHoldNotice.value = false
|
call.showOnHoldNotice.value = false
|
||||||
// 3. Tell Telecom we are active
|
// 3. Tell Telecom we are active
|
||||||
setActive()
|
setActive()
|
||||||
} else {
|
|
||||||
Log.e(TAG, "SIP Resume failed for $callp")
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
Log.e(TAG, "SIP Resume failed for $callp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,9 +18,10 @@ class InCallService : InCallService() {
|
|||||||
Log.d(TAG, "InCallService: Call added")
|
Log.d(TAG, "InCallService: Call added")
|
||||||
|
|
||||||
val handle = call.details.accountHandle
|
val handle = call.details.accountHandle
|
||||||
val baresipHandle = BaresipService.getPhoneAccountHandle(this)
|
val baresipSipHandle = BaresipService.getPhoneAccountHandle(this, BaresipService.SIP_ACCOUNT_ID)
|
||||||
|
val baresipPstnHandle = BaresipService.getPhoneAccountHandle(this, BaresipService.PSTN_ACCOUNT_ID)
|
||||||
|
|
||||||
if (handle == baresipHandle) {
|
if (handle == baresipSipHandle || handle == baresipPstnHandle) {
|
||||||
Log.d(TAG, "InCallService: Identified as SIP call")
|
Log.d(TAG, "InCallService: Identified as SIP call")
|
||||||
// SIP call is already managed by ConnectionService/BaresipService
|
// SIP call is already managed by ConnectionService/BaresipService
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,6 +169,8 @@ import com.tutpro.baresip.CustomElements.PasswordDialog
|
|||||||
import com.tutpro.baresip.CustomElements.SelectableAlertDialog
|
import com.tutpro.baresip.CustomElements.SelectableAlertDialog
|
||||||
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -1204,38 +1206,39 @@ private fun CallUriRow(
|
|||||||
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
|
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
|
||||||
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
|
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = {
|
onValueChange = { input ->
|
||||||
if (isDialer)
|
if (isDialer)
|
||||||
if (it != dialerState.callUri.value) {
|
if (input != dialerState.callUri.value) {
|
||||||
dialerState.callUri.value = it
|
dialerState.callUri.value = input
|
||||||
dialerState.redialUri = ""
|
dialerState.redialUri = ""
|
||||||
if (it == "") {
|
if (input == "") {
|
||||||
dialerState.showCallButton.value = true
|
dialerState.showCallButton.value = true
|
||||||
dialerState.showCallConferenceButton.value = true
|
dialerState.showCallConferenceButton.value = true
|
||||||
}
|
}
|
||||||
if (it.length > 1) {
|
if (input.length > 1) {
|
||||||
val normalizedInput = Utils.unaccent(it)
|
val normalizedInput = Utils.unaccent(input)
|
||||||
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
|
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
|
||||||
filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
|
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
|
||||||
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
||||||
var matchingUri: Contact.ContactUri? = null
|
val uris = contact.uris()
|
||||||
if (numericInput.isNotEmpty()) {
|
val matchingUris = uris.filter { u ->
|
||||||
matchingUri = contact.uris().find { u ->
|
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
|
||||||
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
|
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
|
||||||
}
|
}
|
||||||
}
|
if (nameMatch) {
|
||||||
if (nameMatch || matchingUri != null) {
|
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
|
||||||
val annotatedName = if (nameMatch)
|
if (uris.isEmpty())
|
||||||
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
|
listOf(Triple(contact, annotatedName, null))
|
||||||
else
|
else
|
||||||
AnnotatedString(contact.name())
|
uris.map { Triple(contact, annotatedName, it) }
|
||||||
Triple(contact, annotatedName, matchingUri)
|
}
|
||||||
} else {
|
else if (matchingUris.isNotEmpty())
|
||||||
null
|
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
dialerState.showSuggestions.value = input.length > 1
|
||||||
dialerState.showSuggestions.value = it.length > 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@ -1329,7 +1332,7 @@ private fun CallUriRow(
|
|||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = filteredSuggestions,
|
items = filteredSuggestions,
|
||||||
key = { (contact, _, _) -> "${contact.id()}" }
|
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
|
||||||
) { (contact, annotatedName, matchingUri) ->
|
) { (contact, annotatedName, matchingUri) ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -1348,15 +1351,17 @@ private fun CallUriRow(
|
|||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
if (matchingUri != null) {
|
if (matchingUri != null) {
|
||||||
val tel = matchingUri.uri.substring(4)
|
val uriPart = matchingUri.uri.substringAfter(":")
|
||||||
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
|
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
|
||||||
tel,
|
dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' }
|
||||||
dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' })
|
else
|
||||||
|
dialerState.callUri.value
|
||||||
|
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
|
||||||
Text(
|
Text(
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
if (matchingUri.label.isNotEmpty())
|
if (matchingUri.label.isNotEmpty())
|
||||||
append("${matchingUri.label} ")
|
append("${matchingUri.label} ")
|
||||||
append(annotatedTel)
|
append(annotatedUri)
|
||||||
},
|
},
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@ -1664,29 +1669,30 @@ private fun CallRow(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = transferUri,
|
value = transferUri,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
onValueChange = {
|
onValueChange = { input ->
|
||||||
if (it != transferUri) {
|
if (input != transferUri) {
|
||||||
transferUri = it
|
transferUri = input
|
||||||
if (it.length > 1) {
|
if (input.length > 1) {
|
||||||
val normalizedInput = Utils.unaccent(it)
|
val normalizedInput = Utils.unaccent(input)
|
||||||
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
|
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
|
||||||
filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
|
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
|
||||||
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
|
||||||
var matchingUri: Contact.ContactUri? = null
|
val uris = contact.uris()
|
||||||
if (numericInput.isNotEmpty()) {
|
val matchingUris = uris.filter { u ->
|
||||||
matchingUri = contact.uris().find { u ->
|
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
|
||||||
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
|
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
|
||||||
}
|
}
|
||||||
}
|
if (nameMatch) {
|
||||||
if (nameMatch || matchingUri != null) {
|
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
|
||||||
val annotatedName = if (nameMatch)
|
if (uris.isEmpty())
|
||||||
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
|
listOf(Triple(contact, annotatedName, null))
|
||||||
else
|
else
|
||||||
AnnotatedString(contact.name())
|
uris.map { Triple(contact, annotatedName, it) }
|
||||||
Triple(contact, annotatedName, matchingUri)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
else if (matchingUris.isNotEmpty())
|
||||||
|
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
call.showSuggestions.value = transferUri.length > 1
|
call.showSuggestions.value = transferUri.length > 1
|
||||||
@ -1739,7 +1745,7 @@ private fun CallRow(
|
|||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = filteredSuggestions,
|
items = filteredSuggestions,
|
||||||
key = { (contact, _, _) -> "${contact.id()}" }
|
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
|
||||||
) { (contact, annotatedName, matchingUri) ->
|
) { (contact, annotatedName, matchingUri) ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -1758,15 +1764,17 @@ private fun CallRow(
|
|||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
if (matchingUri != null) {
|
if (matchingUri != null) {
|
||||||
val tel = matchingUri.uri.substring(4)
|
val uriPart = matchingUri.uri.substringAfter(":")
|
||||||
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
|
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
|
||||||
tel,
|
transferUri.filter { c -> c.isDigit() || c == '+' }
|
||||||
transferUri.filter { c -> c.isDigit() || c == '+' })
|
else
|
||||||
|
transferUri
|
||||||
|
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
|
||||||
Text(
|
Text(
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
if (matchingUri.label.isNotEmpty())
|
if (matchingUri.label.isNotEmpty())
|
||||||
append("${matchingUri.label} ")
|
append("${matchingUri.label} ")
|
||||||
append(annotatedTel)
|
append(annotatedUri)
|
||||||
},
|
},
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@ -2150,6 +2158,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
|
|||||||
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Placing Telecom PSTN call to $uri with uap=${ua.uap}")
|
Log.i(TAG, "Placing Telecom PSTN call to $uri with uap=${ua.uap}")
|
||||||
|
val extras = Bundle().apply {
|
||||||
|
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||||
|
BaresipService.getPhoneAccountHandle(ctx, BaresipService.PSTN_ACCOUNT_ID))
|
||||||
|
}
|
||||||
|
val callExtras = Bundle()
|
||||||
|
callExtras.putBoolean("pstnCall", true)
|
||||||
|
callExtras.putString("aor", aor)
|
||||||
|
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
||||||
val telecomUri = if (uri.startsWith("tel:"))
|
val telecomUri = if (uri.startsWith("tel:"))
|
||||||
Uri.fromParts("tel", uri.substring(4), null)
|
Uri.fromParts("tel", uri.substring(4), null)
|
||||||
else
|
else
|
||||||
@ -2166,7 +2182,7 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
|
|||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putParcelable(
|
extras.putParcelable(
|
||||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||||
BaresipService.getPhoneAccountHandle(ctx)
|
BaresipService.getPhoneAccountHandle(ctx, BaresipService.SIP_ACCOUNT_ID)
|
||||||
)
|
)
|
||||||
val callExtras = Bundle()
|
val callExtras = Bundle()
|
||||||
callExtras.putBoolean("conferenceCall", dialerState.showCallConferenceButton.value)
|
callExtras.putBoolean("conferenceCall", dialerState.showCallConferenceButton.value)
|
||||||
@ -2185,6 +2201,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
|
|||||||
Log.e(TAG, error)
|
Log.e(TAG, error)
|
||||||
viewModel.dialerState.callButtonsEnabled.value = true
|
viewModel.dialerState.callButtonsEnabled.value = true
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
delay(5000.milliseconds)
|
||||||
|
if (Call.calls().isEmpty()) {
|
||||||
|
Log.d(TAG, "Re-enabling dialer buttons after timeout (no calls)")
|
||||||
|
viewModel.dialerState.callButtonsEnabled.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2467,7 +2491,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
spinToAor(viewModel, aor, Call.ofCallp(callp))
|
spinToAor(viewModel, aor, Call.ofCallp(callp))
|
||||||
}
|
}
|
||||||
"call redirect" -> {
|
"call redirect" -> {
|
||||||
val redirectUri = ev[1]
|
val redirectUri = Utils.uriUnescape(ev[1])
|
||||||
val target = Utils.friendlyUri(ctx, redirectUri, acc)
|
val target = Utils.friendlyUri(ctx, redirectUri, acc)
|
||||||
if (acc.autoRedirect) {
|
if (acc.autoRedirect) {
|
||||||
redirect(ctx, viewModel, ua, redirectUri)
|
redirect(ctx, viewModel, ua, redirectUri)
|
||||||
@ -2545,7 +2569,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
viewModel.navigateToHome()
|
viewModel.navigateToHome()
|
||||||
val callp = params[1] as Long
|
val callp = params[1] as Long
|
||||||
val call = Call.ofCallp(callp)
|
val call = Call.ofCallp(callp)
|
||||||
val target = Utils.friendlyUri(ctx, ev[1], acc)
|
val target = Utils.friendlyUri(ctx, Utils.uriUnescape(ev[1]), acc)
|
||||||
dialogTitle.value = if (call != null)
|
dialogTitle.value = if (call != null)
|
||||||
ctx.getString(R.string.transfer_request)
|
ctx.getString(R.string.transfer_request)
|
||||||
else
|
else
|
||||||
|
|||||||
@ -212,10 +212,12 @@ object Utils {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
fun e164Uri(uri: String, countryCode: String): String {
|
fun e164Uri(uriText: String, countryCode: String): String {
|
||||||
|
val uri = uriUnescape(uriText)
|
||||||
val scheme = uri.take(4)
|
val scheme = uri.take(4)
|
||||||
val userPart = uriUserPart(uri)
|
val userPart = uriUserPart(uri)
|
||||||
return if (userPart.isNotEmpty() && userPart.isDigitsOnly())
|
val digitsOnlyUserPart = userPart.filter { it.isDigit() }
|
||||||
|
return if (digitsOnlyUserPart.isNotEmpty() && digitsOnlyUserPart.length == userPart.filterNot { it == ' ' || it == '-' || it == '(' || it == ')' || it == '+' }.length)
|
||||||
when {
|
when {
|
||||||
userPart.startsWith("00") -> uri.replace("$scheme$userPart",
|
userPart.startsWith("00") -> uri.replace("$scheme$userPart",
|
||||||
scheme + "+" + userPart.substring(2))
|
scheme + "+" + userPart.substring(2))
|
||||||
@ -238,7 +240,7 @@ object Utils {
|
|||||||
|
|
||||||
fun uriUnescape(uri: String): String {
|
fun uriUnescape(uri: String): String {
|
||||||
return uri.replace("%2B" to "+", "%3A" to ":", "%3B" to ";", "%40" to "@", "%3D" to "=",
|
return uri.replace("%2B" to "+", "%3A" to ":", "%3B" to ";", "%40" to "@", "%3D" to "=",
|
||||||
"%23" to "#", "%2A" to "*")
|
"%23" to "#", "%2A" to "*", "%20" to "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aorDomain(aor: String): String {
|
fun aorDomain(aor: String): String {
|
||||||
|
|||||||
Reference in New Issue
Block a user