Merge branch 'tel_uri'

This commit is contained in:
Juha Heinanen
2022-02-03 12:01:35 +02:00
11 changed files with 322 additions and 168 deletions
@@ -3,6 +3,8 @@ package com.tutpro.baresip
import android.content.Context import android.content.Context
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import java.net.URLEncoder
import java.net.URLDecoder
class Account(val accp: String) { class Account(val accp: String) {
@@ -28,6 +30,7 @@ class Account(val accp: String) {
var missedCalls = false var missedCalls = false
var unreadMessages = false var unreadMessages = false
var callHistory = true var callHistory = true
var telProvider = Utils.aorDomain(aor)
var resumeUri = "" var resumeUri = ""
init { init {
@@ -56,6 +59,10 @@ class Account(val accp: String) {
val extra = Api.account_extra(accp) val extra = Api.account_extra(accp)
callHistory = Utils.paramValue(extra,"call_history") == "" callHistory = Utils.paramValue(extra,"call_history") == ""
telProvider = if (Utils.paramExists(extra, "tel_provider"))
URLDecoder.decode(Utils.paramValue(extra,"tel_provider"), "UTF-8")
else
Utils.aorDomain(aor)
} }
@@ -119,6 +126,8 @@ class Account(val accp: String) {
if (!callHistory) if (!callHistory)
extra += ";call_history=no" extra += ";call_history=no"
extra += ";tel_provider=${URLEncoder.encode(telProvider, "UTF-8")}"
if (extra != "") if (extra != "")
res += ";extra=\"" + extra.substringAfter(";") + "\"" res += ";extra=\"" + extra.substringAfter(";") + "\""
@@ -189,6 +198,14 @@ class Account(val accp: String) {
return res return res
} }
fun telProviderAccounts(): Array<String> {
val res = ArrayList<String>()
for (account in accounts())
if (account.telProvider != "")
res.add(account.aor.substring(4))
return res.toTypedArray()
}
fun ofAor(aor: String): Account? { fun ofAor(aor: String): Account? {
for (ua in UserAgent.uas()) for (ua in UserAgent.uas())
if (ua.account.aor == aor) return ua.account if (ua.account.aor == aor) return ua.account
@@ -2,10 +2,13 @@ package com.tutpro.baresip
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.opengl.Visibility
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.* import android.widget.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.tutpro.baresip.databinding.ActivityAccountBinding import com.tutpro.baresip.databinding.ActivityAccountBinding
@@ -33,6 +36,7 @@ class AccountActivity : AppCompatActivity() {
private lateinit var outbound1: EditText private lateinit var outbound1: EditText
private lateinit var outbound2: EditText private lateinit var outbound2: EditText
private lateinit var mediaNat: String private lateinit var mediaNat: String
private lateinit var stun: LinearLayout
private lateinit var stunServer: EditText private lateinit var stunServer: EditText
private lateinit var stunUser: EditText private lateinit var stunUser: EditText
private lateinit var stunPass: EditText private lateinit var stunPass: EditText
@@ -45,6 +49,7 @@ class AccountActivity : AppCompatActivity() {
private var answerMode = Api.ANSWERMODE_MANUAL private var answerMode = Api.ANSWERMODE_MANUAL
private lateinit var answerModeSpinner: Spinner private lateinit var answerModeSpinner: Spinner
private lateinit var vmUri: EditText private lateinit var vmUri: EditText
private lateinit var telProvider: EditText
private lateinit var defaultCheck: CheckBox private lateinit var defaultCheck: CheckBox
private val mediaEncKeys = arrayListOf("zrtp", "dtls_srtp", "srtp-mandf", "srtp-mand", "srtp", "") private val mediaEncKeys = arrayListOf("zrtp", "dtls_srtp", "srtp-mandf", "srtp-mand", "srtp", "")
@@ -73,6 +78,7 @@ class AccountActivity : AppCompatActivity() {
outbound2 = binding.Outbound2 outbound2 = binding.Outbound2
regCheck = binding.Register regCheck = binding.Register
mediaNatSpinner = binding.mediaNatSpinner mediaNatSpinner = binding.mediaNatSpinner
stun = binding.Stun
stunServer = binding.StunServer stunServer = binding.StunServer
stunUser = binding.StunUser stunUser = binding.StunUser
stunPass = binding.StunPass stunPass = binding.StunPass
@@ -80,6 +86,7 @@ class AccountActivity : AppCompatActivity() {
dtmfModeSpinner = binding.dtmfModeSpinner dtmfModeSpinner = binding.dtmfModeSpinner
answerModeSpinner = binding.answerModeSpinner answerModeSpinner = binding.answerModeSpinner
vmUri = binding.voicemailUri vmUri = binding.voicemailUri
telProvider = binding.telephonyProvider
defaultCheck = binding.Default defaultCheck = binding.Default
aor = intent.getStringExtra("aor")!! aor = intent.getStringExtra("aor")!!
@@ -230,27 +237,21 @@ class AccountActivity : AppCompatActivity() {
stunServer.setText(resources.getString(R.string.stun_server_default)) stunServer.setText(resources.getString(R.string.stun_server_default))
else if ((mediaNat == "ice") && (stunServer.text.toString() == "")) else if ((mediaNat == "ice") && (stunServer.text.toString() == ""))
stunServer.setText(resources.getString(R.string.stun_server_default)) stunServer.setText(resources.getString(R.string.stun_server_default))
else if (mediaNat == "") { if (mediaNat == "") {
stun.visibility = GONE
stunServer.setText("") stunServer.setText("")
stunUser.setText("") stunUser.setText("")
stunPass.setText("") stunPass.setText("")
} } else
stunServer.isEnabled = mediaNat != "" stun.visibility = VISIBLE
stunUser.isEnabled = mediaNat != ""
stunPass.isEnabled = mediaNat != ""
} }
override fun onNothingSelected(parent: AdapterView<*>) { override fun onNothingSelected(parent: AdapterView<*>) {
} }
} }
stunServer.setText(acc.stunServer) stunServer.setText(acc.stunServer)
stunServer.isEnabled = mediaNat != ""
stunUser.setText(acc.stunUser) stunUser.setText(acc.stunUser)
stunUser.isEnabled = mediaNat != ""
stunPass.setText(acc.stunPass) stunPass.setText(acc.stunPass)
stunPass.isEnabled = mediaNat != ""
mediaEnc = acc.mediaEnc mediaEnc = acc.mediaEnc
keyIx = mediaEncKeys.indexOf(mediaEnc) keyIx = mediaEncKeys.indexOf(mediaEnc)
@@ -313,6 +314,8 @@ class AccountActivity : AppCompatActivity() {
} }
} }
telProvider.setText(acc.telProvider)
vmUri.setText(acc.vmUri) vmUri.setText(acc.vmUri)
defaultCheck.isChecked = uaIndex == 0 defaultCheck.isChecked = uaIndex == 0
@@ -563,9 +566,9 @@ class AccountActivity : AppCompatActivity() {
if (tVmUri != "") { if (tVmUri != "") {
if (!tVmUri.startsWith("sip:")) tVmUri = "sip:$tVmUri" if (!tVmUri.startsWith("sip:")) tVmUri = "sip:$tVmUri"
if (!tVmUri.contains("@")) tVmUri = "$tVmUri@${acc.host()}" if (!tVmUri.contains("@")) tVmUri = "$tVmUri@${acc.host()}"
if (!Utils.checkSipUri(tVmUri)) { if (!Utils.checkUri(tVmUri)) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_voicemail_uri), tVmUri)) String.format(getString(R.string.invalid_sip_or_tel_uri), tVmUri))
return false return false
} }
Api.account_set_mwi(acc.accp, "yes") Api.account_set_mwi(acc.accp, "yes")
@@ -576,6 +579,17 @@ class AccountActivity : AppCompatActivity() {
save = true save = true
} }
val hostPart = telProvider.text.toString().trim()
if (hostPart != acc.telProvider) {
if (hostPart != "" && !Utils.checkHostPortParams(hostPart)) {
Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_sip_uri_hostpart), hostPart))
return false
}
acc.telProvider = hostPart
save = true
}
if (defaultCheck.isChecked && (uaIndex > 0)) { if (defaultCheck.isChecked && (uaIndex > 0)) {
val uasTmp = BaresipService.uas[0] val uasTmp = BaresipService.uas[0]
val statusTmp = BaresipService.status[0] val statusTmp = BaresipService.status[0]
@@ -708,6 +722,12 @@ class AccountActivity : AppCompatActivity() {
getString(R.string.voicemain_uri_help) getString(R.string.voicemain_uri_help)
) )
} }
binding.TelephonyProviderTitle.setOnClickListener {
Utils.alertView(
this, getString(R.string.telephony_provider),
getString(R.string.telephony_provider_help)
)
}
binding.DefaultTitle.setOnClickListener { binding.DefaultTitle.setOnClickListener {
Utils.alertView( Utils.alertView(
this, getString(R.string.default_account), this, getString(R.string.default_account),
@@ -123,17 +123,14 @@ class ChatsActivity: AppCompatActivity() {
plusButton.setOnClickListener { plusButton.setOnClickListener {
val uriText = peerUri.text.toString().trim() val uriText = peerUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
var uri = ContactsActivity.findContactURI(uriText) val contactUri = ContactsActivity.findContactUri(uriText)
if (!uri.startsWith("sip:")) { val uri = if (contactUri == null)
uri = "sip:$uri" Utils.uriComplete(uriText, Utils.aorDomain(aor))
if (!uri.contains("@")) { else
val host = aor.substring(aor.indexOf("@") + 1) uriText
uri = "$uri@$host" if (!Utils.checkUri(uri)) {
}
}
if (!Utils.checkSipUri(uri)) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
getString(R.string.invalid_chat_peer_uri)) String.format(getString(R.string.invalid_sip_or_tel_uri), uri))
} else { } else {
peerUri.text.clear() peerUri.text.clear()
peerUri.isCursorVisible = false peerUri.isCursorVisible = false
@@ -33,6 +33,7 @@ import com.tutpro.baresip.databinding.ActivityContactBinding
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
class ContactActivity : AppCompatActivity() { class ContactActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactBinding private lateinit var binding: ActivityContactBinding
@@ -83,12 +84,10 @@ class ContactActivity : AppCompatActivity() {
nameView.hint = getString(R.string.contact_name) nameView.hint = getString(R.string.contact_name)
nameView.setSelection(nameView.text.length) nameView.setSelection(nameView.text.length)
val uri = intent.getStringExtra("uri")!! val uri = intent.getStringExtra("uri")!!
if (uri == "") { if (uri == "")
uriView.setText("") uriView.setText("")
uriView.hint = getString(R.string.sip_uri) else
} else {
uriView.setText(uri) uriView.setText(uri)
}
uOrI = uri uOrI = uri
androidCheck.isChecked = false androidCheck.isChecked = false
} else { } else {
@@ -183,6 +182,9 @@ class ContactActivity : AppCompatActivity() {
requestPermissions(permissions, CONTACT_PERMISSION_REQUEST_CODE) requestPermissions(permissions, CONTACT_PERMISSION_REQUEST_CODE)
} }
// Log.d(TAG, "Android sip contacts ${logAndroidContacts(this, "sip")}")
// Log.d(TAG, "Android tel contacts ${logAndroidContacts(this, "tel")}")
Utils.addActivity("contact,$newContact,$uOrI") Utils.addActivity("contact,$newContact,$uOrI")
} }
@@ -255,7 +257,7 @@ class ContactActivity : AppCompatActivity() {
R.id.checkIcon -> { R.id.checkIcon -> {
var newName = nameView.text.toString().trim() var newName = nameView.text.toString().trim()
var newUri = uriView.text.toString().trim() var newUri = uriView.text.toString().filterNot{setOf('-', ' ').contains(it)}
if (newName == "") newName = newUri if (newName == "") newName = newUri
@@ -275,15 +277,14 @@ class ContactActivity : AppCompatActivity() {
return false return false
} }
if (!newUri.startsWith("sip:")) { if (!newUri.startsWith("sip:") && !newUri.startsWith("tel:"))
newUri = if (newUri.contains("@") || (BaresipService.uas.size != 1)) newUri = if (Utils.isTelNumber(newUri))
"sip:$newUri" "tel:$newUri"
else else
"sip:$newUri@${Utils.aorDomain(BaresipService.uas[0].account.aor)}" "sip:$newUri"
} if (!Utils.checkUri(newUri)) {
if (!Utils.checkSipUri(newUri)) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_contact_uri), newUri)) String.format(getString(R.string.invalid_sip_or_tel_uri), newUri))
return false return false
} }
@@ -433,10 +434,14 @@ class ContactActivity : AppCompatActivity() {
.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name) .withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name)
.build()) .build())
val mimeType = if (contact.uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
ops.add(ContentProviderOperation ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI) .newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0) .withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(Data.MIMETYPE, CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, mimeType)
.withValue(Data.DATA1, contact.uri.substringAfter(":")) .withValue(Data.DATA1, contact.uri.substringAfter(":"))
.build()) .build())
if (contact.avatarImage != null) { if (contact.avatarImage != null) {
@@ -460,37 +465,45 @@ class ContactActivity : AppCompatActivity() {
} }
private fun updateAndroidContact(rawContactId: Long, contact: Contact) { private fun updateAndroidContact(rawContactId: Long, contact: Contact) {
if (updateAndroidSipUri(rawContactId, contact.uri) == 0) if (updateAndroidUri(rawContactId, contact.uri) == 0)
addAndroidSipUri(rawContactId, contact.uri) addAndroidUri(rawContactId, contact.uri)
if (updateAndroidPhoto(rawContactId, contact.avatarImage) == 0) if (updateAndroidPhoto(rawContactId, contact.avatarImage) == 0)
if (contact.avatarImage != null) if (contact.avatarImage != null)
addAndroidPhoto(rawContactId, contact.avatarImage!!) addAndroidPhoto(rawContactId, contact.avatarImage!!)
} }
private fun addAndroidSipUri(rawContactId: Long, sipUri: String) { private fun addAndroidUri(rawContactId: Long, uri: String) {
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val ops = ArrayList<ContentProviderOperation>() val ops = ArrayList<ContentProviderOperation>()
ops.add(ContentProviderOperation ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI) .newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.RAW_CONTACT_ID, rawContactId)
.withValue(Data.MIMETYPE, CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, mimeType)
.withValue(Data.DATA1, sipUri.substringAfter(":")) .withValue(Data.DATA1, uri.substringAfter(":"))
.build()) .build())
try { try {
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Adding of SIP URI $sipUri failed") Log.e(TAG, "Adding of SIP URI $uri failed")
} }
} }
private fun updateAndroidSipUri(rawContactId: Long, sipUri: String): Int { private fun updateAndroidUri(rawContactId: Long, uri: String): Int {
val mimeType = if (uri.startsWith("sip:"))
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val contentValues = ContentValues() val contentValues = ContentValues()
contentValues.put(ContactsContract.Data.DATA1, sipUri) contentValues.put(ContactsContract.Data.DATA1, uri)
val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and " + val where = "${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and " +
"${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE}'" "${ContactsContract.Data.MIMETYPE}='$mimeType'"
return try { return try {
contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null) contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Adding of SIP URI $sipUri failed") Log.e(TAG, "Adding of SIP URI $uri failed")
0 0
} }
} }
@@ -555,13 +568,16 @@ class ContactActivity : AppCompatActivity() {
} }
@Suppress("UNUSED") @Suppress("UNUSED")
fun logAndroidSipContacts(ctx: Context): HashMap<Long, MutableList<String>> { fun logAndroidContacts(ctx: Context, type: String): HashMap<Long, MutableList<String>> {
val mimeType = if (type == "sip")
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
else
CommonDataKinds.Phone.CONTENT_ITEM_TYPE
val contacts = HashMap<Long, MutableList<String>>() val contacts = HashMap<Long, MutableList<String>>()
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME, val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1, ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.PHOTO_THUMBNAIL_URI) ContactsContract.Data.PHOTO_THUMBNAIL_URI)
val selection = ContactsContract.Data.MIMETYPE + "='" + val selection = ContactsContract.Data.MIMETYPE + "='" + mimeType + "'"
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null) selection, null, null)
while (cur != null && cur.moveToNext()) { while (cur != null && cur.moveToNext()) {
@@ -574,6 +590,7 @@ class ContactActivity : AppCompatActivity() {
when (mime) { when (mime) {
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> kind = "sip" CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> kind = "sip"
CommonDataKinds.Photo.CONTENT_ITEM_TYPE -> kind = "thumb" CommonDataKinds.Photo.CONTENT_ITEM_TYPE -> kind = "thumb"
CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> kind = "tel"
} }
Log.d(TAG, "got $id, $name, $kind - $data") Log.d(TAG, "got $id, $name, $kind - $data")
var info: MutableList<String> var info: MutableList<String>
@@ -11,10 +11,7 @@ import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.*
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import java.io.File import java.io.File
@@ -85,7 +82,7 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
} else { } else {
BaresipService.activities.clear() BaresipService.activities.clear()
i.putExtra("uap", ua.uap) i.putExtra("uap", ua.uap)
i.putExtra("peer", Contact.contacts()[position].uri) i.putExtra("peer", contact.uri)
(ctx as Activity).startActivity(i) (ctx as Activity).startActivity(i)
} }
} }
@@ -89,19 +89,18 @@ class ContactsActivity : AppCompatActivity() {
companion object { companion object {
fun findContactURI(name: String): String { fun findContactUri(name: String): String? {
for (c in Contact.contacts()) for (c in Contact.contacts())
if (c.name == name) if (c.name.equals(name, ignoreCase = true))
return c.uri.removePrefix("<") return c.uri.removePrefix("<")
.replaceAfter(">", "") .replaceAfter(">", "")
.replace(">", "") .replace(">", "")
return name return null
} }
fun findContact(uri: String): Contact? { fun findContact(uri: String): Contact? {
for (c in Contact.contacts()) for (c in Contact.contacts())
if ((Utils.uriUserPart(c.uri) == Utils.uriUserPart(uri)) && if (Utils.uriMatch(c.uri, uri))
(Utils.uriHostPart(c.uri) == Utils.uriHostPart(uri)))
return c return c
return null return null
} }
@@ -112,17 +111,14 @@ class ContactsActivity : AppCompatActivity() {
return false return false
} }
// Return contact name of uri or uri itself if contact with uri is not found
fun contactName(uri: String): String { fun contactName(uri: String): String {
val uriUser = Utils.uriUserPart(uri) val userPart = Utils.uriUserPart(uri)
val uriHost = Utils.uriHostPart(uri) return if (Utils.isTelNumber(userPart))
for (c in Contact.contacts()) { findContact("tel:$userPart")?.name ?: uri
val contactUser = Utils.uriUserPart(c.uri) else
if ((contactUser == uriUser) && findContact(uri)?.name ?: uri
(Utils.isE164Number(contactUser) ||
(Utils.uriHostPart(c.uri) == uriHost)))
return c.name
}
return uri
} }
} }
} }
@@ -763,22 +763,36 @@ class MainActivity : AppCompatActivity() {
return return
val uri: Uri? = intent.data val uri: Uri? = intent.data
if (uri != null) { if (uri != null) {
val uriStr = URLDecoder.decode(uri.toString(), "UTF-8").replace(" ", "")
when (uri.scheme) { when (uri.scheme) {
"sip" -> { "sip" -> {
val uriStr = URLDecoder.decode(uri.toString(), "UTF-8")
var ua = UserAgent.ofDomain(Utils.uriHostPart(uriStr)) var ua = UserAgent.ofDomain(Utils.uriHostPart(uriStr))
if (ua == null) if (ua == null && BaresipService.uas.size > 0)
ua = BaresipService.uas[0] ua = BaresipService.uas[0]
if (ua == null) {
Log.w(TAG, "No accounts for '$uriStr'")
return
}
spinToAor(ua.account.aor) spinToAor(ua.account.aor)
resumeAction = "call" resumeAction = "call"
ua.account.resumeUri = uriStr ua.account.resumeUri = uriStr
} }
"tel" -> { "tel" -> {
val acc = BaresipService.uas[0].account val uriStr = URLDecoder.decode(uri.toString(), "UTF-8")
spinToAor(acc.aor) .filterNot{setOf('-', ' ').contains(it)}
var account: Account? = null
for (a in Account.accounts())
if (a.telProvider != "") {
account = a
break
}
if (account == null) {
Log.w(TAG, "No telephony providers for '$uriStr'")
return
}
spinToAor(account.aor)
resumeAction = "call" resumeAction = "call"
acc.resumeUri = uriStr.replace("tel", "sip") + "@" + account.resumeUri = uriStr
Utils.uriHostPart(acc.aor)
} }
else -> { else -> {
Log.w(TAG, "Unsupported URI scheme ${uri.scheme}") Log.w(TAG, "Unsupported URI scheme ${uri.scheme}")
@@ -1369,16 +1383,18 @@ class MainActivity : AppCompatActivity() {
setPositiveButton(R.string.transfer) { dialog, _ -> setPositiveButton(R.string.transfer) { dialog, _ ->
imm.hideSoftInputFromWindow(transferUri.windowToken, 0) imm.hideSoftInputFromWindow(transferUri.windowToken, 0)
dialog.dismiss() dialog.dismiss()
val uriText = transferUri.text.toString().trim() var uriText = transferUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
val uri = Utils.uriComplete( uriText = ContactsActivity.findContactUri(uriText) ?: uriText
ContactsActivity.findContactURI(uriText) if (Utils.isTelNumber(uriText))
.filterNot { it.isWhitespace() }, uriText = "tel:$uriText"
Utils.aorDomain(ua.account.aor) val uri = if (Utils.isTelUri(uriText))
) Utils.telToSip(uriText, ua.account)
if (!Utils.checkSipUri(uri)) { else
Utils.uriComplete(uriText, Utils.aorDomain(ua.account.aor))
if (!Utils.checkUri(uri)) {
Utils.alertView(this@MainActivity, getString(R.string.notice), Utils.alertView(this@MainActivity, getString(R.string.notice),
String.format(getString(R.string.invalid_sip_uri), uri)) String.format(getString(R.string.invalid_sip_or_tel_uri), uri))
} else { } else {
if (Call.uaCalls(ua, "").size > 0) { if (Call.uaCalls(ua, "").size > 0) {
Call.uaCalls(ua, "")[0].transfer(uri) Call.uaCalls(ua, "")[0].transfer(uri)
@@ -1655,17 +1671,38 @@ class MainActivity : AppCompatActivity() {
val ua = UserAgent.uas()[aorSpinner.selectedItemPosition] val ua = UserAgent.uas()[aorSpinner.selectedItemPosition]
val aor = ua.account.aor val aor = ua.account.aor
if (Call.calls().isEmpty()) { if (Call.calls().isEmpty()) {
val uriText = callUri.text.toString().trim() var uriText = callUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
val uri = Utils.uriComplete( uriText = ContactsActivity.findContactUri(uriText) ?: uriText
ContactsActivity.findContactURI(uriText) if (Utils.isTelNumber(uriText))
.filterNot { it.isWhitespace() }, uriText = "tel:$uriText"
Utils.aorDomain(aor) val uri = if (Utils.isTelUri(uriText)) {
) if (ua.account.telProvider == "") {
if (!Utils.checkSipUri(uri)) { val telAccounts = Account.telProviderAccounts()
Utils.alertView( if (telAccounts.isEmpty()) {
this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_sip_uri), uri) getString(R.string.no_telephony_providers))
} else {
val builder = AlertDialog.Builder(this)
with(builder) {
setTitle(getString(R.string.choose_telephony_provider_account))
setItems(telAccounts) { _, which ->
spinToAor("sip:${telAccounts[which]}")
makeCall()
}
setPositiveButton("Cancel") { _: DialogInterface, _: Int -> }
show()
}
}
return
}
Utils.telToSip(uriText, ua.account)
} else {
Utils.uriComplete(uriText, Utils.aorDomain(aor))
}
if (!Utils.checkUri(uri)) {
Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_sip_or_tel_uri), uri)
) )
} else { } else {
callUri.isFocusable = false callUri.isFocusable = false
@@ -1686,8 +1723,7 @@ class MainActivity : AppCompatActivity() {
if (latest != null) if (latest != null)
callUri.setText( callUri.setText(
Utils.friendlyUri( Utils.friendlyUri(
ContactsActivity.contactName(latest.peerUri), ContactsActivity.contactName(latest.peerUri), Utils.aorDomain(aor)
Utils.aorDomain(ua.account.aor)
) )
) )
} }
+49 -14
View File
@@ -105,6 +105,15 @@ object Utils {
"" ""
} }
fun uriMatch(firstUri: String, secondUri: String): Boolean {
if (firstUri.startsWith("tel:"))
return firstUri == secondUri
if (firstUri.startsWith("sip:"))
return uriUserPart(firstUri) == uriUserPart(secondUri) &&
uriHostPart(firstUri) == uriHostPart(secondUri)
return false
}
private fun uriParams(uri: String): List<String> { private fun uriParams(uri: String): List<String> {
val params = uri.split(";") val params = uri.split(";")
return if (params.size == 1) listOf() else params.subList(1, params.size) return if (params.size == 1) listOf() else params.subList(1, params.size)
@@ -121,7 +130,7 @@ object Utils {
return if (u.contains("@")) { return if (u.contains("@")) {
val user = uriUserPart(u) val user = uriUserPart(u)
val host = uriHostPart(u) val host = uriHostPart(u)
if (isE164Number(user) || host == domain) if (isTelNumber(user) || host == domain)
user user
else else
"$user@$host" "$user@$host"
@@ -164,10 +173,6 @@ object Utils {
checkTransport(uri.substringAfter("?"), setOf("udp", "tcp"))) checkTransport(uri.substringAfter("?"), setOf("udp", "tcp")))
} }
fun isE164Number(no: String): Boolean {
return Regex("^[+][1-9][0-9]{0,14}\$").matches(no)
}
fun checkIpV4(ip: String): Boolean { fun checkIpV4(ip: String): Boolean {
return Regex("^(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$").matches(ip) return Regex("^(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$").matches(ip)
} }
@@ -256,6 +261,12 @@ object Utils {
return "" return ""
} }
fun paramExists(params: String, name: String): Boolean {
for (param in params.split(";"))
if (param.substringBefore("=") == name) return true
return false
}
fun checkHostPortParams(hpp: String) : Boolean { fun checkHostPortParams(hpp: String) : Boolean {
val restParams = hpp.split(";", limit = 2) val restParams = hpp.split(";", limit = 2)
return if (restParams.size == 1) return if (restParams.size == 1)
@@ -264,18 +275,42 @@ object Utils {
checkHostPort(restParams[0]) && checkParams(restParams[1]) checkHostPort(restParams[0]) && checkParams(restParams[1])
} }
fun checkSipUri(uri: String): Boolean { private fun checkSipUri(uri: String): Boolean {
if (!uri.startsWith("sip:")) return false return if (uri.startsWith("sip:")) {
val userRest = uri.substring(4).split("@") val userRest = uri.substring(4).split("@")
return when (userRest.size) { when (userRest.size) {
1 -> 1 ->
checkHostPortParams(userRest[0]) checkHostPortParams(userRest[0])
2 -> 2 ->
checkUriUser(userRest[0]) && checkHostPortParams(userRest[1]) checkUriUser(userRest[0]) && checkHostPortParams(userRest[1])
else -> false else -> false
}
} else {
false
} }
} }
fun isTelNumber(no: String): Boolean {
return Regex("^([+][1-9])?[0-9- ]{0,15}\$").matches(no)
}
fun isTelUri(uri: String): Boolean {
return uri.startsWith("tel:") && isTelNumber(uri.substring(4))
}
fun checkUri(uri: String): Boolean {
return checkSipUri(uri) || isTelUri(uri)
}
fun telToSip(telUri: String, account: Account): String {
val hostPart = if (account.telProvider != "")
account.telProvider
else
aorDomain(account.aor)
return "sip:" + telUri.substring(4).filterNot{setOf('-', ' ').contains(it)} +
"@" + hostPart + ";user=phone"
}
fun checkName(name: String): Boolean { fun checkName(name: String): Boolean {
return name.isNotEmpty() && name == String(name.toByteArray(), Charsets.UTF_8) && return name.isNotEmpty() && name == String(name.toByteArray(), Charsets.UTF_8) &&
name.lines().size == 1 && !name.contains('"') name.lines().size == 1 && !name.contains('"')
+83 -56
View File
@@ -178,69 +178,77 @@
android:paddingTop="8dp" > android:paddingTop="8dp" >
</Spinner> </Spinner>
<TextView <LinearLayout
android:id="@+id/StunServerTitle" android:id="@+id/Stun"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:textSize="18sp"
android:text="@string/stun_server" >
</TextView>
<EditText
android:id="@+id/StunServer"
android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:textSize="18sp" android:orientation="vertical"
android:scrollHorizontally="true" android:layout_height="wrap_content" >
android:importantForAutofill="no"
android:hint="@string/stun_server_uri"
android:inputType="textEmailAddress" >
</EditText>
<TextView <TextView
android:id="@+id/StunUserTitle" android:id="@+id/StunServerTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="10dp" android:paddingTop="10dp"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/stun_username" > android:text="@string/stun_server" >
</TextView> </TextView>
<EditText <EditText
android:id="@+id/StunUser" android:id="@+id/StunServer"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:hint="@string/user_id" android:textSize="18sp"
android:textSize="18sp" android:scrollHorizontally="true"
android:inputType="textEmailAddress" android:importantForAutofill="no"
android:autofillHints="username" > android:hint="@string/stun_server_uri"
</EditText> android:inputType="textEmailAddress" >
</EditText>
<TextView <TextView
android:id="@+id/StunPassTitle" android:id="@+id/StunUserTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="10dp" android:paddingTop="10dp"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/stun_password" > android:text="@string/stun_username" >
</TextView> </TextView>
<com.google.android.material.textfield.TextInputLayout <EditText
android:layout_width="match_parent" android:id="@+id/StunUser"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:hintEnabled="false" android:layout_width="fill_parent"
app:endIconMode="password_toggle"> android:hint="@string/user_id"
<androidx.appcompat.widget.AppCompatEditText android:textSize="18sp"
android:id="@+id/StunPass" android:inputType="textEmailAddress"
android:autofillHints="username" >
</EditText>
<TextView
android:id="@+id/StunPassTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:textSize="18sp"
android:text="@string/stun_password" >
</TextView>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:imeOptions="flagNoExtractUi" app:hintEnabled="false"
android:inputType="textPassword" app:endIconMode="password_toggle">
android:hint="@string/password" <androidx.appcompat.widget.AppCompatEditText
android:maxLines="1"> android:id="@+id/StunPass"
</androidx.appcompat.widget.AppCompatEditText> android:layout_width="match_parent"
</com.google.android.material.textfield.TextInputLayout> android:layout_height="wrap_content"
android:imeOptions="flagNoExtractUi"
android:inputType="textPassword"
android:hint="@string/password"
android:maxLines="1">
</androidx.appcompat.widget.AppCompatEditText>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<TextView <TextView
android:id="@+id/DtmfModeTitle" android:id="@+id/DtmfModeTitle"
@@ -293,6 +301,25 @@
android:layout_width="fill_parent"> android:layout_width="fill_parent">
</EditText> </EditText>
<TextView
android:id="@+id/TelephonyProviderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:textSize="18sp"
android:text="@string/telephony_provider" >
</TextView>
<EditText
android:id="@+id/telephonyProvider"
android:layout_height="wrap_content"
android:hint="@string/telephony_provider_hint"
android:textSize="18sp"
android:inputType="textUri"
android:importantForAutofill="no"
android:layout_width="fill_parent">
</EditText>
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
+3 -3
View File
@@ -63,19 +63,19 @@
</EditText> </EditText>
<TextView <TextView
android:id="@+id/SipUriTitle" android:id="@+id/SipOrTelUriTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:text="@string/sip_uri" > android:text="@string/sip_or_tel_uri" >
</TextView> </TextView>
<EditText <EditText
android:id="@+id/Uri" android:id="@+id/Uri"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/sip_uri" android:hint="@string/user_domain_or_number"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
android:textSize="18sp" android:textSize="18sp"
android:importantForAutofill="no" android:importantForAutofill="no"
+13 -1
View File
@@ -182,6 +182,13 @@
messages (Message Waiting Indications) are not subscribed to. messages (Message Waiting Indications) are not subscribed to.
</string> </string>
<string name="invalid_voicemail_uri">Invalid Voicemail URI \'%1$s\'</string> <string name="invalid_voicemail_uri">Invalid Voicemail URI \'%1$s\'</string>
<string name="telephony_provider">Telephony Provider</string>
<string name="telephony_provider_help">SIP URI host part used in calls to telephone numbers.
Factory default is account\'s domain. If not given, this account cannot be used to call
telephone numbers.
</string>
<string name="telephony_provider_hint">SIP URI host part</string>
<string name="invalid_sip_uri_hostpart">Invalid SIP URI host part \'%1$s\'</string>
<string name="default_account">Default Account</string> <string name="default_account">Default Account</string>
<string name="default_account_help">If checked, this account is selected when baresip is started. <string name="default_account_help">If checked, this account is selected when baresip is started.
</string> </string>
@@ -247,7 +254,7 @@
<string name="today">Today</string> <string name="today">Today</string>
<string name="you">You</string> <string name="you">You</string>
<string name="new_chat_peer">New Chat Peer</string> <string name="new_chat_peer">New Chat Peer</string>
<string name="invalid_chat_peer_uri">Invalid SIP URI</string> <string name="invalid_chat_peer_uri">Invalid chat peer URI</string>
<string name="long_chat_question">Do you want to delete chat with peer \'%1$s\' or <string name="long_chat_question">Do you want to delete chat with peer \'%1$s\' or
add peer to contacts?</string> add peer to contacts?</string>
<string name="short_chat_question">Do you want to delete chat with \'%1$s\'?</string> <string name="short_chat_question">Do you want to delete chat with \'%1$s\'?</string>
@@ -341,6 +348,8 @@
<string name="contact">Contact</string> <string name="contact">Contact</string>
<string name="new_contact">New Contact</string> <string name="new_contact">New Contact</string>
<string name="contact_name">Name</string> <string name="contact_name">Name</string>
<string name="sip_or_tel_uri">SIP or tel URI</string>
<string name="user_domain_or_number">user@domain or telephone number</string>
<string name="invalid_contact">Invalid contact name \'%1$s\'</string> <string name="invalid_contact">Invalid contact name \'%1$s\'</string>
<string name="contact_already_exists">Contact \'%1$s\' already exists.</string> <string name="contact_already_exists">Contact \'%1$s\' already exists.</string>
<string name="invalid_contact_uri">Invalid SIP URI</string> <string name="invalid_contact_uri">Invalid SIP URI</string>
@@ -353,6 +362,7 @@
<string name="send_message">Send Message</string> <string name="send_message">Send Message</string>
<string name="contact_delete_question">Do you want to delete contact \'%1$s\'?</string> <string name="contact_delete_question">Do you want to delete contact \'%1$s\'?</string>
<string name="contacts_exceeded">Your maximum number of contacts %1$d has been exceeded.</string> <string name="contacts_exceeded">Your maximum number of contacts %1$d has been exceeded.</string>
<string name="no_telephony_providers">Telephony Provider has not been configured for any account.</string>
<!-- Generic --> <!-- Generic -->
<string name="alert">Alert</string> <string name="alert">Alert</string>
<string name="info">Info</string> <string name="info">Info</string>
@@ -376,6 +386,7 @@
<string name="confirmation">Confirmation</string> <string name="confirmation">Confirmation</string>
<string name="dots" translatable="false"></string> <string name="dots" translatable="false"></string>
<string name="bullet_item" translatable="false">\u2022 %1$s</string> <string name="bullet_item" translatable="false">\u2022 %1$s</string>
<string name="invalid_sip_or_tel_uri">Invalid SIP or tel URI \'%1$s\'</string>
<!-- Main Activity --> <!-- Main Activity -->
<string name="backup">Backup</string> <string name="backup">Backup</string>
<string name="restore">Restore</string> <string name="restore">Restore</string>
@@ -386,6 +397,7 @@
<string name="incoming_call_from_dots">Call from …</string> <string name="incoming_call_from_dots">Call from …</string>
<string name="transferring_call_to_dots">Transferring call to …</string> <string name="transferring_call_to_dots">Transferring call to …</string>
<string name="invalid_sip_uri">Invalid SIP URI \'%1$s\'</string> <string name="invalid_sip_uri">Invalid SIP URI \'%1$s\'</string>
<string name="choose_telephony_provider_account">Choose Telephony Provider account.</string>
<string name="callee">Callee</string> <string name="callee">Callee</string>
<string name="hangup">Hangup</string> <string name="hangup">Hangup</string>
<string name="video_call">Video call</string> <string name="video_call">Video call</string>