Replaced Show Android Contacts setting with Contacts setting with possible

values 'baresip', 'Android', and Both
This commit is contained in:
Juha Heinanen
2022-02-17 18:00:44 +02:00
parent 24dbb94c31
commit e47f7bf855
24 changed files with 619 additions and 774 deletions

View File

@ -85,7 +85,7 @@
<activity <activity
android:name=".ContactsActivity" android:name=".ContactsActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/baresip_contacts" android:label="@string/contacts"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustPan" > android:windowSoftInputMode="adjustPan" >
</activity> </activity>
@ -95,18 +95,11 @@
android:label="@string/contact" android:label="@string/contact"
android:parentActivityName=".ContactsActivity" > android:parentActivityName=".ContactsActivity" >
</activity> </activity>
<activity
android:name=".AndroidContactsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/android_contacts"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustPan" >
</activity>
<activity <activity
android:name=".AndroidContactActivity" android:name=".AndroidContactActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/contact" android:label="@string/contact"
android:parentActivityName=".AndroidContactsActivity" > android:parentActivityName=".ContactsActivity" >
</activity> </activity>
<activity <activity
android:name=".ConfigActivity" android:name=".ConfigActivity"

View File

@ -1,17 +0,0 @@
package com.tutpro.baresip
import android.net.Uri
import java.util.ArrayList
class AndroidContact(val id: Long, var name: String, var color: Int, var thumbnailUri: Uri?) {
val uris = ArrayList<String>()
companion object {
fun contacts(): ArrayList<AndroidContact> {
return BaresipService.androidContacts
}
}
}

View File

@ -26,7 +26,6 @@ class AndroidContactActivity : AppCompatActivity() {
private var index = 0 private var index = 0
private var color = 0 private var color = 0
private var id: Long = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -43,10 +42,9 @@ class AndroidContactActivity : AppCompatActivity() {
cardImageAvatarView = binding.ImageAvatar cardImageAvatarView = binding.ImageAvatar
nameView = binding.Name nameView = binding.Name
val contact = BaresipService.androidContacts[index] val contact = Contact.contacts()[index] as Contact.AndroidContact
val name = contact.name val name = contact.name
color = contact.color color = contact.color
id = contact.id
val thumbnailUri = contact.thumbnailUri val thumbnailUri = contact.thumbnailUri
if (thumbnailUri != null) if (thumbnailUri != null)
showImageAvatar(thumbnailUri) showImageAvatar(thumbnailUri)
@ -105,5 +103,3 @@ class AndroidContactActivity : AppCompatActivity() {
} }
} }

View File

@ -1,75 +0,0 @@
package com.tutpro.baresip
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity
class AndroidContactListAdapter(private val ctx: Context,
private val rows: ArrayList<AndroidContact>,
private val aor: String) :
ArrayAdapter<AndroidContact>(ctx, R.layout.android_contact_row, rows) {
private val layoutInflater = LayoutInflater.from(context)
private var lastClick: Long = 0
private class ViewHolder(view: View?) {
val textAvatarView = view?.findViewById(R.id.TextAvatar) as TextView
val imageAvatarView = view?.findViewById(R.id.ImageAvatar) as ImageView
val nameView = view?.findViewById(R.id.contactName) as TextView
}
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
val viewHolder: ViewHolder
val rowView: View
if (view == null) {
rowView = layoutInflater.inflate(R.layout.android_contact_row, parent, false)
viewHolder = ViewHolder(rowView)
rowView.tag = viewHolder
} else {
rowView = view
viewHolder = rowView.tag as ViewHolder
}
val contact = rows[position]
val thumbNailUri = contact.thumbnailUri
if (thumbNailUri != null) {
viewHolder.imageAvatarView.setImageURI(thumbNailUri)
} else {
viewHolder.textAvatarView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
viewHolder.textAvatarView.text = "${contact.name[0]}"
else
viewHolder.textAvatarView.text = ""
viewHolder.imageAvatarView.setImageBitmap(Utils.bitmapFromView(viewHolder.textAvatarView))
}
viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f
viewHolder.nameView.setPadding(6, 6, 0, 6)
viewHolder.nameView.setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
val i = Intent(ctx, AndroidContactActivity::class.java)
val b = Bundle()
b.putString("aor", aor)
b.putInt("index", position)
i.putExtras(b)
startActivity(ctx, i, null)
}
}
return rowView
}
}

View File

@ -1,228 +0,0 @@
package com.tutpro.baresip
import android.Manifest
import android.app.Activity
import android.content.*
import android.content.pm.PackageManager
import android.database.Cursor
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.provider.ContactsContract
import android.view.Menu
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
import android.widget.RelativeLayout
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import com.google.android.material.snackbar.Snackbar
import com.tutpro.baresip.Utils.showSnackBar
import com.tutpro.baresip.databinding.ActivityAndroidContactsBinding
class AndroidContactsActivity : AppCompatActivity() {
private lateinit var binding: ActivityAndroidContactsBinding
private lateinit var layout: RelativeLayout
private lateinit var clAdapter: AndroidContactListAdapter
private lateinit var aor: String
private var lastSwap: Long = 0
private lateinit var permissionsLauncher: ActivityResultLauncher<Array<String>>
private val permissions = arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAndroidContactsBinding.inflate(layoutInflater)
setContentView(binding.root)
layout = binding.AndroidContactsView
aor = intent.getStringExtra("aor")!!
val listView = binding.androidContacts
permissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
var allowed = true
permissions.entries.forEach {
allowed = allowed && it.value
}
if (allowed) {
fetchAndroidContacts(this)
clAdapter.notifyDataSetChanged()
}
}
if (Build.VERSION.SDK_INT >= 23 && !Utils.checkPermissions(this, permissions))
requestPermissions(permissions, CONTACT_PERMISSION_REQUEST_CODE)
clAdapter = AndroidContactListAdapter(this, BaresipService.androidContacts, aor)
listView.adapter = clAdapter
Utils.addActivity("android contacts,$aor")
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.swap_contacts_icon, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.swapIcon -> {
if (SystemClock.elapsedRealtime() - lastSwap > 1000) {
lastSwap = SystemClock.elapsedRealtime()
BaresipService.activities.remove("android contacts,$aor")
val intent = Intent(this, ContactsActivity::class.java)
intent.putExtra("aor", aor)
startActivity(intent)
finish()
return true
}
}
android.R.id.home -> {
BaresipService.activities.remove("android contacts,$aor")
setResult(Activity.RESULT_OK, Intent())
finish()
}
}
return true
}
override fun onBackPressed() {
BaresipService.activities.remove("android contacts,$aor")
setResult(Activity.RESULT_OK, Intent())
finish()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grandResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grandResults)
when (requestCode) {
CONTACT_PERMISSION_REQUEST_CODE -> {
var allowed = true
for (res in grandResults)
allowed = allowed && res == PackageManager.PERMISSION_GRANTED
if (allowed) {
fetchAndroidContacts(this)
clAdapter.notifyDataSetChanged()
} else {
when {
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
permissionsLauncher.launch(permissions)
}
}
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
permissionsLauncher.launch(permissions)
}
}
else -> {
permissionsLauncher.launch(permissions)
}
}
}
}
}
}
companion object {
fun fetchAndroidContacts(ctx: Context) {
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.PHOTO_THUMBNAIL_URI)
val selection =
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null)
val contacts = HashMap<Long, AndroidContact>()
while (cur != null && cur.moveToNext()) {
val id = cur.getLong(0)
val name = cur.getString(1) ?: ""
val mime = cur.getString(2)
val data = cur.getString(3)
val thumb = cur.getString(4)?.toUri()
val contact = if (contacts.containsKey(id))
contacts[id]!!
else
AndroidContact(id, name, Utils.randomColor(), thumb)
if (contact.name == "" && name != "")
contact.name = name
if (contact.thumbnailUri == null && thumb != null)
contact.thumbnailUri = thumb
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
contact.uris.add("tel:${data.filterNot{setOf('-', ' ').contains(it)}}")
else if (mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
contact.uris.add("sip:$data")
else
continue
if (!contacts.containsKey(id))
contacts[id] = contact
}
cur?.close()
BaresipService.androidContacts.clear()
for ((_, value) in contacts)
if (value.name != "")
BaresipService.androidContacts.add(value)
Utils.reloadContactNames()
return
}
fun findContact(uri: String): AndroidContact? {
for (c in BaresipService.androidContacts)
for (u in c.uris)
if (Utils.uriMatch(u, uri))
return c
return null
}
// Return contact name of uri or uri itself if contact with uri is not found
fun contactName(uri: String): String {
val userPart = Utils.uriUserPart(uri)
return if (Utils.isTelNumber(userPart))
findContact("tel:$userPart")?.name ?: uri
else
findContact(uri)?.name ?: uri
}
// Return first sip (preferred) or tel uri of contact name or
// null if contact is not found or if it has no uris
fun contactUri(name: String): String? {
for (c in BaresipService.androidContacts)
if (c.name == name) {
if (c.uris.isNotEmpty()) {
for (u in c.uris)
if (u.startsWith("sip:"))
return u
return c.uris.first()
}
}
return null
}
}
}

View File

@ -41,6 +41,7 @@ import java.io.File
import java.net.InetAddress import java.net.InetAddress
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
import kotlin.collections.ArrayList
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -74,6 +75,7 @@ class BaresipService: Service() {
private var hotSpotIsEnabled = false private var hotSpotIsEnabled = false
private var hotSpotAddresses = mapOf<String, String>() private var hotSpotAddresses = mapOf<String, String>()
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var contentObserverRegistered = false
@SuppressLint("WakelockTimeout") @SuppressLint("WakelockTimeout")
override fun onCreate() { override fun onCreate() {
@ -299,33 +301,28 @@ class BaresipService: Service() {
this.registerReceiver(bluetoothReceiver, filter) this.registerReceiver(bluetoothReceiver, filter)
} }
val contentObserver: ContentObserver = object : ContentObserver(null) { contentObserver = object : ContentObserver(null) {
override fun onChange(self: Boolean) { override fun onChange(self: Boolean) {
Log.d(TAG, "Contacts change") Log.d(TAG, "Contacts change")
AndroidContactsActivity.fetchAndroidContacts(this@BaresipService.applicationContext) if (contactsMode != "baresip") {
Contact.loadAndroidContacts(this@BaresipService.applicationContext)
Contact.contactsUpdate()
}
} }
} }
try {
AndroidContactsActivity.fetchAndroidContacts(this)
contentResolver.registerContentObserver(
ContactsContract.Contacts.CONTENT_URI, true, contentObserver)
} catch(e: SecurityException) {
Log.i(TAG, "No Contacts permission")
}
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action: String val action: String?
if (intent == null) { if (intent == null) {
action = "Start" action = "Start"
Log.d(TAG, "Received onStartCommand with null intent") Log.d(TAG, "Received onStartCommand with null intent")
} else { } else {
// Utils.dumpIntent(intent) // Utils.dumpIntent(intent)
action = intent.action!! action = intent.action
Log.d(TAG, "Received onStartCommand action $action") Log.d(TAG, "Received onStartCommand action $action")
} }
@ -359,13 +356,21 @@ class BaresipService: Service() {
Log.i(TAG, "Asset '$a' already copied") Log.i(TAG, "Asset '$a' already copied")
} }
if (a == "config") if (a == "config")
Config.initialize() Config.initialize(applicationContext)
} }
if (File(filesDir, "history").exists()) if (File(filesDir, "history").exists())
File(filesDir, "history").renameTo(File(filesDir, "calls")) File(filesDir, "history").renameTo(File(filesDir, "calls"))
Contact.restore()
if (contactsMode != "Android")
Contact.restoreBaresipContacts()
if (contactsMode != "baresip") {
Contact.loadAndroidContacts(applicationContext)
registerContentObserver()
}
Contact.contactsUpdate()
val history = CallHistory.get() val history = CallHistory.get()
if (history.isEmpty()) { if (history.isEmpty()) {
NewCallHistory.restore() NewCallHistory.restore()
@ -417,6 +422,14 @@ class BaresipService: Service() {
} }
"Start Content Observer" -> {
registerContentObserver()
}
"Stop Content Observer" -> {
unRegisterContentObserver()
}
"Call Answer" -> { "Call Answer" -> {
val uap = intent!!.getStringExtra("uap")!! val uap = intent!!.getStringExtra("uap")!!
val callp = intent.getStringExtra("callp")!! val callp = intent.getStringExtra("callp")!!
@ -666,7 +679,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent, PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent,
PendingIntent.FLAG_UPDATE_CURRENT) PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val caller = Utils.friendlyUri(Utils.contactName(peerUri), Utils.aorDomain(aor)) val caller = Utils.friendlyUri(Contact.contactName(peerUri), Utils.aorDomain(aor))
nb.setSmallIcon(R.drawable.ic_stat_call) nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip)) .setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi) .setContentIntent(pi)
@ -760,7 +773,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, TRANSFER_REQ_CODE, PendingIntent.getActivity(applicationContext, TRANSFER_REQ_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT) intent, PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val target = Utils.friendlyUri(Utils.contactName(ev[1]), Utils.aorDomain(aor)) val target = Utils.friendlyUri(Contact.contactName(ev[1]), Utils.aorDomain(aor))
nb.setSmallIcon(R.drawable.ic_stat_call) nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip)) .setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi) .setContentIntent(pi)
@ -836,7 +849,7 @@ class BaresipService: Service() {
} }
if (!Utils.isVisible()) { if (!Utils.isVisible()) {
if (missed) { if (missed) {
val caller = Utils.friendlyUri(Utils.contactName(call.peerUri), Utils.aorDomain(aor)) val caller = Utils.friendlyUri(Contact.contactName(call.peerUri), Utils.aorDomain(aor))
val intent = Intent(applicationContext, MainActivity::class.java) val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
@ -942,7 +955,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, MESSAGE_REQ_CODE, intent, PendingIntent.getActivity(applicationContext, MESSAGE_REQ_CODE, intent,
PendingIntent.FLAG_UPDATE_CURRENT) PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val sender = Utils.friendlyUri(Utils.contactName(peer), Utils.aorDomain(ua.account.aor)) val sender = Utils.friendlyUri(Contact.contactName(peer), Utils.aorDomain(ua.account.aor))
nb.setSmallIcon(R.drawable.ic_stat_message) nb.setSmallIcon(R.drawable.ic_stat_message)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip)) .setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi) .setContentIntent(pi)
@ -1389,6 +1402,24 @@ class BaresipService: Service() {
return false return false
} }
private fun registerContentObserver() {
if (!contentObserverRegistered)
try {
contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, contentObserver)
contentObserverRegistered = true
} catch (e: SecurityException) {
Log.i(TAG, "No Contacts permission")
}
}
private fun unRegisterContentObserver() {
if (contentObserverRegistered) {
contentResolver.unregisterContentObserver(contentObserver)
contentObserverRegistered = false
}
}
private fun cleanService() { private fun cleanService() {
am.mode = AudioManager.MODE_NORMAL am.mode = AudioManager.MODE_NORMAL
abandonAudioFocus() abandonAudioFocus()
@ -1429,7 +1460,6 @@ class BaresipService: Service() {
var callActionUri = "" var callActionUri = ""
var isMainVisible = false var isMainVisible = false
var isMicMuted = false var isMicMuted = false
var preferAndroidContacts = false
val uas = ArrayList<UserAgent>() val uas = ArrayList<UserAgent>()
val status = ArrayList<Int>() val status = ArrayList<Int>()
@ -1437,10 +1467,13 @@ class BaresipService: Service() {
var callHistory = ArrayList<NewCallHistory>() var callHistory = ArrayList<NewCallHistory>()
var messages = ArrayList<Message>() var messages = ArrayList<Message>()
val messageUpdate = MutableLiveData<Long>() val messageUpdate = MutableLiveData<Long>()
val contactUpdate = MutableLiveData<Long>()
val registrationUpdate = MutableLiveData<Long>() val registrationUpdate = MutableLiveData<Long>()
val baresipContacts = ArrayList<Contact.BaresipContact>()
val androidContacts = ArrayList<Contact.AndroidContact>()
val contacts = ArrayList<Contact>() val contacts = ArrayList<Contact>()
val androidContacts = ArrayList<AndroidContact>() val contactNames = ArrayList<String>()
val contactNames = mutableListOf<String>() var contactsMode = "baresip"
val chatTexts: MutableMap<String, String> = mutableMapOf() val chatTexts: MutableMap<String, String> = mutableMapOf()
val activities = mutableListOf<String>() val activities = mutableListOf<String>()
var dnsServers = listOf<InetAddress>() var dnsServers = listOf<InetAddress>()

View File

@ -62,7 +62,7 @@ class CallListAdapter(private val ctx: Context, private val aor: String, private
if (count <= 3) if (count <= 3)
viewHolder.etcView.text = "" viewHolder.etcView.text = ""
val contactName = Utils.contactName(callRow.peerUri) val contactName = Contact.contactName(callRow.peerUri)
if (contactName.startsWith("sip:")) if (contactName.startsWith("sip:"))
viewHolder.peerURIView.text = Utils.friendlyUri(contactName, Utils.aorDomain(callRow.aor)) viewHolder.peerURIView.text = Utils.friendlyUri(contactName, Utils.aorDomain(callRow.aor))
else else

View File

@ -50,7 +50,7 @@ class CallsActivity : AppCompatActivity() {
listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ ->
val peerUri = uaHistory[pos].peerUri val peerUri = uaHistory[pos].peerUri
var peerName = Utils.contactName(peerUri) var peerName = Contact.contactName(peerUri)
if (peerName.startsWith("sip:")) if (peerName.startsWith("sip:"))
peerName = Utils.friendlyUri(peerName, Utils.aorDomain(aor)) peerName = Utils.friendlyUri(peerName, Utils.aorDomain(aor))
val dialogClickListener = DialogInterface.OnClickListener { _, which -> val dialogClickListener = DialogInterface.OnClickListener { _, which ->
@ -95,7 +95,7 @@ class CallsActivity : AppCompatActivity() {
listView.onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, pos, _ -> listView.onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, pos, _ ->
val peerUri = uaHistory[pos].peerUri val peerUri = uaHistory[pos].peerUri
val peerName = Utils.contactName(peerUri) val peerName = Contact.contactName(peerUri)
val dialogClickListener = DialogInterface.OnClickListener { _, which -> val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) { when (which) {
DialogInterface.BUTTON_NEGATIVE -> { DialogInterface.BUTTON_NEGATIVE -> {

View File

@ -63,7 +63,7 @@ class ChatActivity : AppCompatActivity() {
imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
this@ChatActivity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) this@ChatActivity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
var chatPeer = Utils.contactName(peerUri) var chatPeer = Contact.contactName(peerUri)
if (chatPeer.startsWith("sip:")) if (chatPeer.startsWith("sip:"))
chatPeer = Utils.friendlyUri(chatPeer, Utils.aorDomain(aor)) chatPeer = Utils.friendlyUri(chatPeer, Utils.aorDomain(aor))
@ -112,7 +112,7 @@ class ChatActivity : AppCompatActivity() {
} }
} }
val builder = AlertDialog.Builder(this@ChatActivity, R.style.Theme_AppCompat) val builder = AlertDialog.Builder(this@ChatActivity, R.style.Theme_AppCompat)
if (Utils.contactName(peerUri) == peerUri) if (Contact.contactName(peerUri) == peerUri)
with (builder) { with (builder) {
setMessage(String.format(getString(R.string.long_message_question), setMessage(String.format(getString(R.string.long_message_question),
chatPeer)) chatPeer))

View File

@ -52,7 +52,7 @@ class ChatListAdapter(private val ctx: Context, private val rows: ArrayList<Mess
else else
viewHolder.layoutView.setBackgroundResource(R.drawable.message_out_bg) viewHolder.layoutView.setBackgroundResource(R.drawable.message_out_bg)
val peerName = Utils.contactName(message.peerUri) val peerName = Contact.contactName(message.peerUri)
viewHolder.peerView.text = if (peerName !=message.peerUri ) viewHolder.peerView.text = if (peerName !=message.peerUri )
peerName peerName
else else

View File

@ -78,14 +78,14 @@ class ChatsActivity: AppCompatActivity() {
} }
DialogInterface.BUTTON_NEGATIVE -> { DialogInterface.BUTTON_NEGATIVE -> {
val peerUri = uaMessages[pos].peerUri val peerUri = uaMessages[pos].peerUri
val msgs = ArrayList<Message>() val messages = ArrayList<Message>()
for (m in Message.messages()) for (m in Message.messages())
if ((m.aor != aor) || (m.peerUri != peerUri)) if ((m.aor != aor) || (m.peerUri != peerUri))
msgs.add(m) messages.add(m)
else else
clAdapter.remove(m) clAdapter.remove(m)
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
BaresipService.messages = msgs BaresipService.messages = messages
Message.save() Message.save()
uaMessages = uaMessages(aor) uaMessages = uaMessages(aor)
} }
@ -95,7 +95,7 @@ class ChatsActivity: AppCompatActivity() {
} }
val builder = AlertDialog.Builder(this@ChatsActivity, R.style.Theme_AppCompat) val builder = AlertDialog.Builder(this@ChatsActivity, R.style.Theme_AppCompat)
val peer = Utils.contactName(uaMessages[pos].peerUri) val peer = Contact.contactName(uaMessages[pos].peerUri)
if (peer.startsWith("sip:")) if (peer.startsWith("sip:"))
with (builder) { with (builder) {
setMessage(String.format(getString(R.string.long_chat_question), setMessage(String.format(getString(R.string.long_chat_question),
@ -118,12 +118,12 @@ class ChatsActivity: AppCompatActivity() {
peerUri = binding.peer peerUri = binding.peer
peerUri.threshold = 2 peerUri.threshold = 2
peerUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, peerUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames())) Contact.contactNames()))
plusButton.setOnClickListener { plusButton.setOnClickListener {
val uriText = peerUri.text.toString().trim() val uriText = peerUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
var uri = Utils.contactUri(uriText) var uri = Contact.contactUri(uriText)
if (uri == null) if (uri == null)
uri = if (Utils.isTelNumber(uriText)) uri = if (Utils.isTelNumber(uriText))
"tel:$uriText" "tel:$uriText"

View File

@ -1,5 +1,6 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.Manifest
import android.content.Context import android.content.Context
import java.net.InetAddress import java.net.InetAddress
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -9,7 +10,7 @@ object Config {
private val configPath = BaresipService.filesPath + "/config" private val configPath = BaresipService.filesPath + "/config"
private var config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1) private var config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1)
fun initialize() { fun initialize(ctx: Context) {
config = config.replace("module_tmp uuid.so", "module uuid.so") config = config.replace("module_tmp uuid.so", "module uuid.so")
@ -85,7 +86,19 @@ object Config {
config = "${config}jitter_buffer_wish 6\n" config = "${config}jitter_buffer_wish 6\n"
} }
BaresipService.preferAndroidContacts = config.contains("prefer_android_contacts yes") removeVariable("prefer_android_contacts")
if (config.contains("contacts_mode")) {
BaresipService.contactsMode = variable("contacts_mode")[0]
if (BaresipService.contactsMode != "baresip" &&
!Utils.checkPermissions(ctx, arrayOf(Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS))) {
BaresipService.contactsMode = "baresip"
replaceVariable("contacts_mode", "baresip")
}
} else {
BaresipService.contactsMode = "baresip"
}
Utils.putFileContents(configPath, config.toByteArray()) Utils.putFileContents(configPath, config.toByteArray())
BaresipService.isConfigInitialized = true BaresipService.isConfigInitialized = true

View File

@ -32,6 +32,7 @@ class ConfigActivity : AppCompatActivity() {
private lateinit var binding: ActivityConfigBinding private lateinit var binding: ActivityConfigBinding
private lateinit var layout: ScrollView private lateinit var layout: ScrollView
private lateinit var baresipService: Intent
private lateinit var autoStart: CheckBox private lateinit var autoStart: CheckBox
private lateinit var batteryOptimizations: CheckBox private lateinit var batteryOptimizations: CheckBox
private lateinit var listenAddr: EditText private lateinit var listenAddr: EditText
@ -40,11 +41,14 @@ class ConfigActivity : AppCompatActivity() {
private lateinit var verifyServer: CheckBox private lateinit var verifyServer: CheckBox
private lateinit var caFile: CheckBox private lateinit var caFile: CheckBox
private lateinit var darkTheme: CheckBox private lateinit var darkTheme: CheckBox
private lateinit var androidContacts: CheckBox private lateinit var contactsSpinner: Spinner
private lateinit var contactsMode: String
private lateinit var contactsModes: ArrayList<String>
private lateinit var debug: CheckBox private lateinit var debug: CheckBox
private lateinit var sipTrace: CheckBox private lateinit var sipTrace: CheckBox
private lateinit var reset: CheckBox private lateinit var reset: CheckBox
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String> private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var oldAutoStart = "" private var oldAutoStart = ""
private var oldListenAddr = "" private var oldListenAddr = ""
@ -54,7 +58,7 @@ class ConfigActivity : AppCompatActivity() {
private var oldCAFile = false private var oldCAFile = false
private var oldLogLevel = "" private var oldLogLevel = ""
private var oldDisplayTheme = -1 private var oldDisplayTheme = -1
private var oldAndroidContacts = "" private var oldContactsMode = ""
private var save = false private var save = false
private var restart = false private var restart = false
private var menu: Menu? = null private var menu: Menu? = null
@ -63,12 +67,16 @@ class ConfigActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val contactsPermissions = arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
binding = ActivityConfigBinding.inflate(layoutInflater) binding = ActivityConfigBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
layout = binding.ConfigView layout = binding.ConfigView
Utils.addActivity("config") Utils.addActivity("config")
baresipService = Intent(this@ConfigActivity, BaresipService::class.java)
autoStart = binding.AutoStart autoStart = binding.AutoStart
val asCv = Config.variable("auto_start") val asCv = Config.variable("auto_start")
oldAutoStart = if (asCv.size == 0) "no" else asCv[0] oldAutoStart = if (asCv.size == 0) "no" else asCv[0]
@ -291,10 +299,27 @@ class ConfigActivity : AppCompatActivity() {
oldDisplayTheme = Preferences(applicationContext).displayTheme oldDisplayTheme = Preferences(applicationContext).displayTheme
darkTheme.isChecked = oldDisplayTheme == AppCompatDelegate.MODE_NIGHT_YES darkTheme.isChecked = oldDisplayTheme == AppCompatDelegate.MODE_NIGHT_YES
androidContacts = binding.AndroidContacts contactsModes = arrayListOf(
val acCv = Config.variable("prefer_android_contacts") getString(R.string.baresip), getString(R.string.android), getString(R.string.both))
oldAndroidContacts = if (acCv.size == 0) "no" else acCv[0] contactsSpinner = binding.contactsSpinner
androidContacts.isChecked = oldAndroidContacts == "yes" val ctCv = Config.variable("contacts_mode")
oldContactsMode = if (ctCv.size == 0) "baresip" else ctCv[0]
contactsMode = oldContactsMode
contactsModes.removeAt(contactsModes.indexOf(oldContactsMode))
contactsModes.add(0, oldContactsMode)
val contactsAdapter = ArrayAdapter(this,android.R.layout.simple_spinner_item,
contactsModes)
contactsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
contactsSpinner.adapter = contactsAdapter
contactsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
contactsMode = contactsModes[position]
if (contactsMode != "baresip" && Build.VERSION.SDK_INT >= 23 &&
!Utils.checkPermissions(applicationContext, contactsPermissions))
requestPermissions(contactsPermissions, CONTACTS_PERMISSION_REQUEST_CODE)
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
debug = binding.Debug debug = binding.Debug
val dbCv = Config.variable("log_level") val dbCv = Config.variable("log_level")
@ -340,7 +365,14 @@ class ConfigActivity : AppCompatActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
requestPermissionLauncher = requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {} registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
if (it.containsValue(false)) {
contactsMode = oldContactsMode
contactsSpinner.setSelection(contactsModes.indexOf(oldContactsMode))
}
}
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -356,6 +388,44 @@ class ConfigActivity : AppCompatActivity() {
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grandResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grandResults)
when (requestCode) {
CONTACTS_PERMISSION_REQUEST_CODE -> {
if (grandResults.contains(PackageManager.PERMISSION_DENIED)) {
when {
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
requestPermissionsLauncher.launch(permissions)
}
}
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
requestPermissionsLauncher.launch(permissions)
}
}
else -> {
requestPermissionsLauncher.launch(permissions)
}
}
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (BaresipService.activities.indexOf("config") == -1) return true if (BaresipService.activities.indexOf("config") == -1) return true
@ -433,13 +503,28 @@ class ConfigActivity : AppCompatActivity() {
if (oldDisplayTheme != newDisplayTheme) if (oldDisplayTheme != newDisplayTheme)
Preferences(applicationContext).displayTheme = newDisplayTheme Preferences(applicationContext).displayTheme = newDisplayTheme
val androidContactsString = if (androidContacts.isChecked) if (oldContactsMode != contactsMode) {
"yes" Config.replaceVariable("contacts_mode", contactsMode)
else BaresipService.contactsMode = contactsMode
"no" when (contactsMode) {
if (oldAndroidContacts != androidContactsString) { "baresip" -> {
Config.replaceVariable("prefer_android_contacts", androidContactsString) BaresipService.androidContacts.clear()
BaresipService.preferAndroidContacts = androidContacts.isChecked Contact.restoreBaresipContacts()
baresipService.action = "Stop Content Observer"
}
"Android" -> {
BaresipService.baresipContacts.clear()
Contact.loadAndroidContacts(this)
baresipService.action = "Start Content Observer"
}
"Both" -> {
Contact.restoreBaresipContacts()
Contact.loadAndroidContacts(this)
baresipService.action = "Start Content Observer"
}
}
Contact.contactsUpdate()
startService(baresipService)
save = true save = true
} }
@ -530,9 +615,9 @@ class ConfigActivity : AppCompatActivity() {
Utils.alertView(this, getString(R.string.dark_theme), Utils.alertView(this, getString(R.string.dark_theme),
getString(R.string.dark_theme_help)) getString(R.string.dark_theme_help))
} }
binding.AndroidContactsTitle.setOnClickListener { binding.ContactsTitle.setOnClickListener {
Utils.alertView(this, getString(R.string.show_android_contacts), Utils.alertView(this, getString(R.string.contacts),
getString(R.string.show_android_contacts_help)) getString(R.string.contacts_help))
} }
binding.DebugTitle.setOnClickListener { binding.DebugTitle.setOnClickListener {
Utils.alertView(this, getString(R.string.debug), getString(R.string.debug_help)) Utils.alertView(this, getString(R.string.debug), getString(R.string.debug_help))

View File

@ -6,7 +6,7 @@ const val DEFAULT_CHANNEL_ID = "com.tutpro.baresip.default"
const val HIGH_CHANNEL_ID = "com.tutpro.baresip.high" const val HIGH_CHANNEL_ID = "com.tutpro.baresip.high"
const val MIC_PERMISSION_REQUEST_CODE = 1 const val MIC_PERMISSION_REQUEST_CODE = 1
const val CONTACT_PERMISSION_REQUEST_CODE = 2 const val CONTACTS_PERMISSION_REQUEST_CODE = 2
const val STATUS_NOTIFICATION_ID = 101 const val STATUS_NOTIFICATION_ID = 101
const val CALL_NOTIFICATION_ID = 102 const val CALL_NOTIFICATION_ID = 102

View File

@ -1,39 +1,165 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.content.Context
import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.ContactsContract
import androidx.core.net.toUri
import java.io.File import java.io.File
import java.util.ArrayList import java.util.ArrayList
class Contact(var name: String, var uri: String, var color: Int, val id: Long) { sealed class Contact {
var avatarImage: Bitmap? = null class BaresipContact(var name: String, var uri: String, var color: Int, val id: Long): Contact() {
var androidContact = false var avatarImage: Bitmap? = null
}
class AndroidContact(var name: String, var color: Int, var thumbnailUri: Uri?): Contact() {
val uris = ArrayList<String>()
}
companion object { companion object {
const val CONTACTS_SIZE = 100 const val CONTACTS_SIZE = 256
fun contacts(): ArrayList<Contact> { fun contacts(): ArrayList<Contact> {
return BaresipService.contacts return BaresipService.contacts
} }
fun save(): Boolean { fun contactNames(): ArrayList<String> {
return BaresipService.contactNames
}
// Return contact name of uri or uri itself if contact with uri is not found
fun contactName(uri: String): String {
val userPart = Utils.uriUserPart(uri)
val contact = if (Utils.isTelNumber(userPart))
findContact("tel:$userPart")
else
findContact(uri)
if (contact != null) {
return when (contact) {
is BaresipContact ->
contact.name
is AndroidContact ->
contact.name
}
}
return uri
}
// Return uri of contact name or null if contact is not found
fun contactUri(name: String): String? {
for (c in contacts())
when (c) {
is BaresipContact -> {
if (c.name.equals(name, ignoreCase = true))
return c.uri.removePrefix("<")
.replaceAfter(">", "")
.replace(">", "")
}
is AndroidContact -> {
if (c.name == name) {
if (c.uris.isNotEmpty()) {
for (u in c.uris)
if (u.startsWith("sip:"))
return u
return c.uris.first()
}
}
}
}
return null
}
fun findContact(uri: String): Contact? {
for (c in contacts())
when (c) {
is BaresipContact -> {
if (Utils.uriMatch(c.uri, uri))
return c
}
is AndroidContact -> {
for (u in c.uris)
if (Utils.uriMatch(u, uri))
return c
}
}
return null
}
fun nameExists(name: String, ignoreCase: Boolean): Boolean {
for (c in BaresipService.baresipContacts)
if (c.name.equals(name, ignoreCase = ignoreCase))
return true
for (c in BaresipService.androidContacts)
if (c.name.equals(name, ignoreCase = ignoreCase))
return true
return false
}
fun saveBaresipContacts() {
var contents = "" var contents = ""
for (c in BaresipService.contacts) contents += for (c in BaresipService.contacts)
"\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color};android=${c.androidContact}\n" if (c is BaresipContact)
return Utils.putFileContents(BaresipService.filesPath + "/contacts", contents += "\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color}\n"
Utils.putFileContents(BaresipService.filesPath + "/contacts",
contents.toByteArray()) contents.toByteArray())
} }
fun restore(): Boolean { fun loadAndroidContacts(ctx: Context) {
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.PHOTO_THUMBNAIL_URI)
val selection =
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null)
BaresipService.androidContacts.clear()
val contacts = HashMap<Long, AndroidContact>()
while (cur != null && cur.moveToNext()) {
val id = cur.getLong(0)
val name = cur.getString(1) ?: ""
val mime = cur.getString(2)
val data = cur.getString(3)
val thumb = cur.getString(4)?.toUri()
if (nameExists(name, true)) {
Log.d(TAG, "Skipping Android contact with existing name '$name'")
continue
}
val contact = if (contacts.containsKey(id))
contacts[id]!!
else
AndroidContact(name, Utils.randomColor(), thumb)
if (contact.name == "" && name != "")
contact.name = name
if (contact.thumbnailUri == null && thumb != null)
contact.thumbnailUri = thumb
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
contact.uris.add("tel:${data.filterNot{setOf('-', ' ').contains(it)}}")
else if (mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
contact.uris.add("sip:$data")
else
continue
if (!contacts.containsKey(id))
contacts[id] = contact
}
cur?.close()
for ((_, value) in contacts)
if (value.name != "")
BaresipService.androidContacts.add(value)
}
fun restoreBaresipContacts(): Boolean {
val content = Utils.getFileContents(BaresipService.filesPath + "/contacts") val content = Utils.getFileContents(BaresipService.filesPath + "/contacts")
?: return false ?: return false
val contacts = String(content) val contacts = String(content)
BaresipService.contacts.clear()
var contactNo = 0 var contactNo = 0
val baseId = System.currentTimeMillis() val baseId = System.currentTimeMillis()
BaresipService.baresipContacts.clear()
contacts.lines().forEach { contacts.lines().forEach {
val parts = it.split("\"") val parts = it.split("\"")
if (parts.size == 3) { if (parts.size == 3) {
@ -47,15 +173,13 @@ class Contact(var name: String, var uri: String, var color: Int, val id: Long) {
colorValue.toInt() colorValue.toInt()
else else
Utils.randomColor() Utils.randomColor()
val androidContact = Utils.paramValue(params, "android" ) == "true"
val idValue = Utils.paramValue(params, "id" ) val idValue = Utils.paramValue(params, "id" )
val id: Long = if (idValue != "") val id: Long = if (idValue != "")
idValue.toLong() idValue.toLong()
else else
baseId + contactNo baseId + contactNo
Log.d(TAG, "Restoring contact $name, $uri, $color, $id, $androidContact") Log.d(TAG, "Restoring contact $name, $uri, $color, $id")
val contact = Contact(name, uri, color, id) val contact = BaresipContact(name, uri, color, id)
contact.androidContact = androidContact
val avatarFilePath = BaresipService.filesPath + "/$id.png" val avatarFilePath = BaresipService.filesPath + "/$id.png"
if (File(avatarFilePath).exists()) { if (File(avatarFilePath).exists()) {
try { try {
@ -67,12 +191,38 @@ class Contact(var name: String, var uri: String, var color: Int, val id: Long) {
Log.e(TAG, "Could not read avatar image from '$id.img") Log.e(TAG, "Could not read avatar image from '$id.img")
} }
} }
BaresipService.contacts.add(contact) BaresipService.baresipContacts.add(contact)
} }
} }
Utils.reloadContactNames()
return true return true
} }
fun contactsUpdate() {
BaresipService.contacts.clear()
if (BaresipService.contactsMode != "Android")
BaresipService.contacts.addAll(BaresipService.baresipContacts)
if (BaresipService.contactsMode != "baresip")
BaresipService.contacts.addAll(BaresipService.androidContacts)
sortContacts()
generateContactNames()
BaresipService.contactUpdate.postValue(System.nanoTime())
}
fun sortContacts() {
BaresipService.contacts.sortBy{ when (it) {
is BaresipContact -> it.name
is AndroidContact -> it.name
}}
}
fun generateContactNames () {
BaresipService.contactNames.clear()
BaresipService.contactNames.addAll(
BaresipService.contacts.map {
when (it) {
is BaresipContact -> it.name
is AndroidContact -> it.name
}})
}
} }
} }

View File

@ -1,18 +1,15 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ContentProviderOperation import android.content.ContentProviderOperation
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.ContactsContract import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds import android.provider.ContactsContract.CommonDataKinds
@ -21,14 +18,10 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.google.android.material.snackbar.Snackbar
import com.tutpro.baresip.Utils.showSnackBar
import com.tutpro.baresip.databinding.ActivityContactBinding import com.tutpro.baresip.databinding.ActivityContactBinding
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@ -44,7 +37,6 @@ class ContactActivity : AppCompatActivity() {
private lateinit var uriView: EditText private lateinit var uriView: EditText
private lateinit var androidCheck: CheckBox private lateinit var androidCheck: CheckBox
private lateinit var menu: Menu private lateinit var menu: Menu
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var newContact = false private var newContact = false
private var newAvatar = "" private var newAvatar = ""
@ -53,10 +45,6 @@ class ContactActivity : AppCompatActivity() {
private var index = 0 private var index = 0
private var color = 0 private var color = 0
private var id: Long = 0 private var id: Long = 0
private var oldAndroid = false
private val permissions =
arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -75,6 +63,10 @@ class ContactActivity : AppCompatActivity() {
newContact = intent.getBooleanExtra("new", false) newContact = intent.getBooleanExtra("new", false)
if (newContact) { if (newContact) {
if (BaresipService.contactsMode == "baresip") {
binding.AndroidTitle.visibility = View.GONE
androidCheck.visibility = View.GONE
}
title = getString(R.string.new_contact) title = getString(R.string.new_contact)
color = Utils.randomColor() color = Utils.randomColor()
id = System.currentTimeMillis() id = System.currentTimeMillis()
@ -88,27 +80,33 @@ class ContactActivity : AppCompatActivity() {
else else
uriView.setText(uri) uriView.setText(uri)
uOrI = uri uOrI = uri
androidCheck.isChecked = false if ((BaresipService.contactsMode == "Android")) {
androidCheck.isChecked = true
androidCheck.isClickable = false
} else {
androidCheck.isChecked = false
}
} else { } else {
binding.AndroidTitle.visibility = View.GONE
androidCheck.visibility = View.GONE
index = intent.getIntExtra("index", 0) index = intent.getIntExtra("index", 0)
val contact = Contact.contacts()[index] val contact = Contact.contacts()[index]
val name = contact.name if (contact is Contact.BaresipContact) {
color = contact.color val name = contact.name
id = contact.id color = contact.color
val avatarImage = contact.avatarImage id = contact.id
if (avatarImage != null) val avatarImage = contact.avatarImage
showImageAvatar(avatarImage) if (avatarImage != null)
else showImageAvatar(avatarImage)
showTextAvatar(name, color) else
title = name showTextAvatar(name, color)
nameView.setText(name) title = name
uriView.setText(contact.uri) nameView.setText(name)
uOrI = index.toString() uriView.setText(contact.uri)
androidCheck.isChecked = contact.androidContact uOrI = index.toString()
}
} }
oldAndroid = androidCheck.isChecked
val avatarRequest = val avatarRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) { if (it.resultCode == Activity.RESULT_OK) {
@ -175,12 +173,6 @@ class ContactActivity : AppCompatActivity() {
getString(R.string.android_contact_help)) getString(R.string.android_contact_help))
} }
androidCheck.setOnClickListener{
if (Build.VERSION.SDK_INT >= 23)
if (!Utils.checkPermissions(this, permissions))
requestPermissions(permissions, CONTACT_PERMISSION_REQUEST_CODE)
}
// Log.d(TAG, "Android sip contacts ${logAndroidContacts(this, "sip")}") // Log.d(TAG, "Android sip contacts ${logAndroidContacts(this, "sip")}")
// Log.d(TAG, "Android tel contacts ${logAndroidContacts(this, "tel")}") // Log.d(TAG, "Android tel contacts ${logAndroidContacts(this, "tel")}")
@ -188,12 +180,6 @@ class ContactActivity : AppCompatActivity() {
} }
override fun onStart() {
super.onStart()
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
}
override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean { override fun onCreateOptionsMenu(optionsMenu: Menu): Boolean {
super.onCreateOptionsMenu(optionsMenu) super.onCreateOptionsMenu(optionsMenu)
@ -205,48 +191,6 @@ class ContactActivity : AppCompatActivity() {
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grandResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grandResults)
when (requestCode) {
CONTACT_PERMISSION_REQUEST_CODE -> {
var allowed = true
for (res in grandResults)
allowed = allowed && res == PackageManager.PERMISSION_GRANTED
if (!allowed) {
androidCheck.isChecked = oldAndroid
when {
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
requestPermissionsLauncher.launch(permissions)
}
}
ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_CONTACTS) -> {
layout.showSnackBar(
binding.root,
getString(R.string.no_android_contacts),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
requestPermissionsLauncher.launch(permissions)
}
}
else -> {
requestPermissionsLauncher.launch(permissions)
}
}
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (BaresipService.activities.indexOf("contact,$newContact,$uOrI") == -1) return true if (BaresipService.activities.indexOf("contact,$newContact,$uOrI") == -1) return true
@ -265,11 +209,13 @@ class ContactActivity : AppCompatActivity() {
String.format(getString(R.string.invalid_contact), newName)) String.format(getString(R.string.invalid_contact), newName))
return false return false
} }
val alert: Boolean = if (newContact) val alert: Boolean = if (newContact)
ContactsActivity.nameExists(newName, true) Contact.nameExists(newName, true)
else else {
(Contact.contacts()[index].name != newName) && val c = Contact.contacts()[index] as Contact.BaresipContact
ContactsActivity.nameExists(newName, false) (c.name != newName) && Contact.nameExists(newName, false)
}
if (alert) { if (alert) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.contact_already_exists), newName)) String.format(getString(R.string.contact_already_exists), newName))
@ -287,7 +233,8 @@ class ContactActivity : AppCompatActivity() {
return false return false
} }
val contact: Contact val contact: Contact.BaresipContact
if (newContact) { if (newContact) {
if (Contact.contacts().size >= Contact.CONTACTS_SIZE) { if (Contact.contacts().size >= Contact.CONTACTS_SIZE) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
@ -296,16 +243,13 @@ class ContactActivity : AppCompatActivity() {
BaresipService.activities.removeAt(0) BaresipService.activities.removeAt(0)
return true return true
} else { } else {
contact = Contact(newName, newUri, color, id) contact = Contact.BaresipContact(newName, newUri, color, id)
contact.androidContact = androidCheck.isChecked
Contact.contacts().add(contact)
} }
} else { } else {
contact = Contact.contacts()[index] contact = Contact.contacts()[index] as Contact.BaresipContact
contact.uri = newUri contact.uri = newUri
contact.name = newName contact.name = newName
contact.color = color contact.color = color
contact.androidContact = androidCheck.isChecked
} }
when (newAvatar) { when (newAvatar) {
@ -323,21 +267,18 @@ class ContactActivity : AppCompatActivity() {
} }
} }
Contact.contacts().sortBy { Contact -> Contact.name } if (androidCheck.isChecked) {
addOrUpdateAndroidContact(this, contact)
if (Utils.checkPermissions(this, permissions)) {
if (contact.androidContact)
addOrUpdateAndroidContact(this, contact)
else if (oldAndroid)
deleteAndroidContact(this, contact)
} else { } else {
contact.androidContact = oldAndroid if (newContact)
Contact.contacts().add(contact)
Contact.sortContacts()
Contact.generateContactNames()
Contact.saveBaresipContacts()
} }
Contact.save()
Utils.reloadContactNames()
BaresipService.activities.remove("contact,$newContact,$uOrI") BaresipService.activities.remove("contact,$newContact,$uOrI")
val i = Intent(this, MainActivity::class.java) val i = Intent(this, MainActivity::class.java)
i.putExtra("name", newName) i.putExtra("name", newName)
setResult(Activity.RESULT_OK, i) setResult(Activity.RESULT_OK, i)
@ -407,7 +348,7 @@ class ContactActivity : AppCompatActivity() {
return rotatedBitmap return rotatedBitmap
} }
private fun addOrUpdateAndroidContact(ctx: Context, contact: Contact) { private fun addOrUpdateAndroidContact(ctx: Context, contact: Contact.BaresipContact) {
val projection = arrayOf(ContactsContract.Data.RAW_CONTACT_ID) val projection = arrayOf(ContactsContract.Data.RAW_CONTACT_ID)
val selection = ContactsContract.Data.MIMETYPE + "='" + val selection = ContactsContract.Data.MIMETYPE + "='" +
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " + CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
@ -420,10 +361,9 @@ class ContactActivity : AppCompatActivity() {
addAndroidContact(ctx, contact) addAndroidContact(ctx, contact)
} }
c?.close() c?.close()
contact.androidContact = true
} }
private fun addAndroidContact(ctx: Context, contact: Contact): Boolean { private fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Boolean {
val ops = ArrayList<ContentProviderOperation>() val ops = ArrayList<ContentProviderOperation>()
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
@ -464,7 +404,7 @@ class ContactActivity : AppCompatActivity() {
return true return true
} }
private fun updateAndroidContact(rawContactId: Long, contact: Contact) { private fun updateAndroidContact(rawContactId: Long, contact: Contact.BaresipContact) {
if (updateAndroidUri(rawContactId, contact.uri) == 0) if (updateAndroidUri(rawContactId, contact.uri) == 0)
addAndroidUri(rawContactId, contact.uri) addAndroidUri(rawContactId, contact.uri)
if (updateAndroidPhoto(rawContactId, contact.avatarImage) == 0) if (updateAndroidPhoto(rawContactId, contact.avatarImage) == 0)
@ -561,9 +501,9 @@ class ContactActivity : AppCompatActivity() {
var contact: Contact? = null var contact: Contact? = null
fun deleteAndroidContact(ctx: Context, contact: Contact): Int { fun deleteAndroidContact(ctx: Context, name: String): Int {
return ctx.contentResolver.delete(ContactsContract.RawContacts.CONTENT_URI, return ctx.contentResolver.delete(ContactsContract.RawContacts.CONTENT_URI,
ContactsContract.Contacts.DISPLAY_NAME + "='" + contact.name + "'", ContactsContract.Contacts.DISPLAY_NAME + "='" + name + "'",
null) null)
} }

View File

@ -1,22 +1,22 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
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.* import android.widget.ArrayAdapter
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.*
class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<Contact>, class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<Contact>,
private val aor: String) : private val aor: String) :
@ -48,82 +48,137 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
val contact = rows[position] val contact = rows[position]
val avatarImage = contact.avatarImage if (contact is Contact.BaresipContact) {
if (avatarImage != null) {
viewHolder.imageAvatarView.setImageBitmap(avatarImage)
} else {
viewHolder.textAvatarView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
viewHolder.textAvatarView.text = "${contact.name[0]}"
else
viewHolder.textAvatarView.text = ""
viewHolder.imageAvatarView.setImageBitmap(Utils.bitmapFromView(viewHolder.textAvatarView))
}
viewHolder.nameView.text = contact.name val avatarImage = contact.avatarImage
viewHolder.nameView.textSize = 20f if (avatarImage != null) {
viewHolder.nameView.setPadding(6, 6, 0, 6) viewHolder.imageAvatarView.setImageBitmap(avatarImage)
} else {
viewHolder.textAvatarView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
viewHolder.textAvatarView.text = "${contact.name[0]}"
else
viewHolder.textAvatarView.text = ""
viewHolder.imageAvatarView.setImageBitmap(Utils.bitmapFromView(viewHolder.textAvatarView))
}
if (aor != "") { viewHolder.nameView.text = contact.name
viewHolder.nameView.setOnClickListener { viewHolder.nameView.textSize = 20f
val dialogClickListener = DialogInterface.OnClickListener { _, which -> viewHolder.nameView.setPadding(6, 6, 0, 6)
when (which) {
DialogInterface.BUTTON_POSITIVE, DialogInterface.BUTTON_NEGATIVE -> { if (aor != "") {
val i = Intent(ctx, MainActivity::class.java) viewHolder.nameView.setOnClickListener {
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or val dialogClickListener = DialogInterface.OnClickListener { _, which ->
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP when (which) {
if (which == DialogInterface.BUTTON_NEGATIVE) DialogInterface.BUTTON_POSITIVE, DialogInterface.BUTTON_NEGATIVE -> {
i.putExtra("action", "call") val i = Intent(ctx, MainActivity::class.java)
else i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
i.putExtra("action", "message") Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
val ua = UserAgent.ofAor(aor) if (which == DialogInterface.BUTTON_NEGATIVE)
if (ua == null) { i.putExtra("action", "call")
Log.w(TAG, "onClickListener did not find AoR $aor") else
} else { i.putExtra("action", "message")
BaresipService.activities.clear() val ua = UserAgent.ofAor(aor)
i.putExtra("uap", ua.uap) if (ua == null) {
i.putExtra("peer", contact.uri) Log.w(TAG, "onClickListener did not find AoR $aor")
(ctx as Activity).startActivity(i) } else {
BaresipService.activities.clear()
i.putExtra("uap", ua.uap)
i.putExtra("peer", contact.uri)
(ctx as Activity).startActivity(i)
}
}
DialogInterface.BUTTON_NEUTRAL -> {
} }
} }
DialogInterface.BUTTON_NEUTRAL -> {
}
} }
} if (SystemClock.elapsedRealtime() - lastClick > 1000) {
if (SystemClock.elapsedRealtime() - lastClick > 1000) { lastClick = SystemClock.elapsedRealtime()
lastClick = SystemClock.elapsedRealtime() with(AlertDialog.Builder(ctx, R.style.Theme_AppCompat)) {
with (AlertDialog.Builder(ctx, R.style.Theme_AppCompat)) { setMessage(String.format(ctx.getString(R.string.contact_action_question),
setMessage(String.format(ctx.getString(R.string.contact_action_question), contact.name))
Contact.contacts()[position].name)) setNeutralButton(ctx.getText(R.string.cancel), dialogClickListener)
setNeutralButton(ctx.getText(R.string.cancel), dialogClickListener) setNegativeButton(ctx.getText(R.string.call), dialogClickListener)
setNegativeButton(ctx.getText(R.string.call), dialogClickListener) setPositiveButton(ctx.getText(R.string.send_message), dialogClickListener)
setPositiveButton(ctx.getText(R.string.send_message), dialogClickListener) show()
show() }
} }
} }
} }
viewHolder.actionView.visibility = View.VISIBLE
viewHolder.actionView.setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
val i = Intent(ctx, ContactActivity::class.java)
val b = Bundle()
b.putBoolean("new", false)
b.putInt("index", position)
i.putExtras(b)
startActivity(ctx, i, null)
}
}
}
if (contact is Contact.AndroidContact) {
val thumbNailUri = contact.thumbnailUri
if (thumbNailUri != null) {
viewHolder.imageAvatarView.setImageURI(thumbNailUri)
} else {
viewHolder.textAvatarView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
viewHolder.textAvatarView.text = "${contact.name[0]}"
else
viewHolder.textAvatarView.text = ""
viewHolder.imageAvatarView.setImageBitmap(Utils.bitmapFromView(viewHolder.textAvatarView))
}
viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f
viewHolder.nameView.setPadding(6, 6, 0, 6)
viewHolder.nameView.setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
val i = Intent(ctx, AndroidContactActivity::class.java)
val b = Bundle()
b.putString("aor", aor)
b.putInt("index", position)
i.putExtras(b)
startActivity(ctx, i, null)
}
}
viewHolder.actionView.visibility = View.GONE
} }
viewHolder.nameView.setOnLongClickListener { viewHolder.nameView.setOnLongClickListener {
val dialogClickListener = DialogInterface.OnClickListener { _, which -> val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) { when (which) {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
val id = contact.id when (contact) {
val avatarFile = File(BaresipService.filesPath, "$id.img") is Contact.BaresipContact -> {
if (avatarFile.exists()) { val id = contact.id
try { val avatarFile = File(BaresipService.filesPath, "$id.img")
avatarFile.delete() if (avatarFile.exists()) {
} catch (e: IOException) { try {
Log.e(TAG, "Could not delete file '$id.img") avatarFile.delete()
} catch (e: IOException) {
Log.e(TAG, "Could not delete file '$id.img")
}
}
Contact.contacts().removeAt(position)
Contact.contactNames().removeAt(position)
Contact.saveBaresipContacts()
this.notifyDataSetChanged()
}
is Contact.AndroidContact -> {
ContactActivity.deleteAndroidContact(ctx, contact.name)
} }
} }
if (contact.androidContact &&
Utils.checkPermissions(ctx, arrayOf(Manifest.permission.WRITE_CONTACTS)))
ContactActivity.deleteAndroidContact(ctx, contact)
Contact.contacts().removeAt(position)
Contact.save()
Utils.reloadContactNames()
this.notifyDataSetChanged()
} }
DialogInterface.BUTTON_NEGATIVE -> { DialogInterface.BUTTON_NEGATIVE -> {
} }
@ -131,10 +186,13 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
} }
val titleView = View.inflate(ctx, R.layout.alert_title, null) as TextView val titleView = View.inflate(ctx, R.layout.alert_title, null) as TextView
titleView.text = ctx.getString(R.string.confirmation) titleView.text = ctx.getString(R.string.confirmation)
with (AlertDialog.Builder(ctx)) { with(AlertDialog.Builder(ctx)) {
setCustomTitle(titleView) setCustomTitle(titleView)
setMessage(String.format(ctx.getString(R.string.contact_delete_question), setMessage(String.format(ctx.getString(R.string.contact_delete_question),
Contact.contacts()[position].name)) when (contact) {
is Contact.BaresipContact -> contact.name
is Contact.AndroidContact -> contact.name
}))
setNegativeButton(ctx.getText(R.string.cancel), dialogClickListener) setNegativeButton(ctx.getText(R.string.cancel), dialogClickListener)
setPositiveButton(ctx.getText(R.string.delete), dialogClickListener) setPositiveButton(ctx.getText(R.string.delete), dialogClickListener)
show() show()
@ -142,18 +200,6 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
true true
} }
viewHolder.actionView.setOnClickListener {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
val i = Intent(ctx, ContactActivity::class.java)
val b = Bundle()
b.putBoolean("new", false)
b.putInt("index", position)
i.putExtras(b)
startActivity(ctx, i, null)
}
}
return rowView return rowView
} }
} }

View File

@ -4,10 +4,10 @@ import android.app.Activity
import android.content.* import android.content.*
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.view.Menu
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.Observer
import com.tutpro.baresip.databinding.ActivityContactsBinding import com.tutpro.baresip.databinding.ActivityContactsBinding
class ContactsActivity : AppCompatActivity() { class ContactsActivity : AppCompatActivity() {
@ -27,10 +27,15 @@ class ContactsActivity : AppCompatActivity() {
Utils.addActivity("contacts,$aor") Utils.addActivity("contacts,$aor")
val listView = binding.contacts val listView = binding.contacts
clAdapter = ContactListAdapter(this, Contact.contacts(), aor) clAdapter = ContactListAdapter(this, BaresipService.contacts, aor)
listView.adapter = clAdapter listView.adapter = clAdapter
listView.isLongClickable = true listView.isLongClickable = true
val contactObserver = Observer<Long> {
clAdapter.notifyDataSetChanged()
}
BaresipService.contactUpdate.observe(this, contactObserver)
val contactRequest = val contactRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) if (it.resultCode == RESULT_OK)
@ -58,34 +63,14 @@ class ContactsActivity : AppCompatActivity() {
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.swap_contacts_icon, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.swapIcon -> {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
BaresipService.activities.remove("contacts,$aor")
val intent = Intent(this, AndroidContactsActivity::class.java)
intent.putExtra("aor", aor)
startActivity(intent)
finish()
return true
}
}
android.R.id.home -> { android.R.id.home -> {
BaresipService.activities.remove("contacts,$aor") BaresipService.activities.remove("contacts,$aor")
setResult(Activity.RESULT_OK, Intent()) setResult(Activity.RESULT_OK, Intent())
@ -105,40 +90,4 @@ class ContactsActivity : AppCompatActivity() {
} }
companion object {
// Return uri of contact name or null if contact is not found
fun contactUri(name: String): String? {
for (c in Contact.contacts())
if (c.name.equals(name, ignoreCase = true))
return c.uri.removePrefix("<")
.replaceAfter(">", "")
.replace(">", "")
return null
}
// Return contact name of uri or null if contact with uri is not found
fun contactName(uri: String): String? {
val userPart = Utils.uriUserPart(uri)
return if (Utils.isTelNumber(userPart))
findContact("tel:$userPart")?.name
else
findContact(uri)?.name
}
fun findContact(uri: String): Contact? {
for (c in Contact.contacts()) {
if (Utils.uriMatch(c.uri, uri))
return c
}
return null
}
fun nameExists(name: String, ignoreCase: Boolean): Boolean {
for (c in Contact.contacts())
if (c.name.equals(name, ignoreCase = ignoreCase)) return true
return false
}
}
} }

View File

@ -209,11 +209,13 @@ class MainActivity : AppCompatActivity() {
// Haven't found any explanation why this can happen. // Haven't found any explanation why this can happen.
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
Log.d(TAG, "aorSpinner selecting $position") Log.d(TAG, "aorSpinner selecting $position")
val acc = UserAgent.uas()[position].account if (position < UserAgent.uas().size) {
aorSpinner.tag = acc.aor val acc = UserAgent.uas()[position].account
val ua = UserAgent.uas()[position] aorSpinner.tag = acc.aor
showCall(ua) val ua = UserAgent.uas()[position]
updateIcons(acc) showCall(ua)
updateIcons(acc)
}
} }
override fun onNothingSelected(parent: AdapterView<*>) { override fun onNothingSelected(parent: AdapterView<*>) {
Log.d(TAG, "Nothing selected") Log.d(TAG, "Nothing selected")
@ -273,7 +275,7 @@ class MainActivity : AppCompatActivity() {
} }
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames())) Contact.contactNames()))
callUri.threshold = 2 callUri.threshold = 2
callUri.setOnFocusChangeListener { view, b -> callUri.setOnFocusChangeListener { view, b ->
if (b) { if (b) {
@ -456,27 +458,18 @@ class MainActivity : AppCompatActivity() {
contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames())) Contact.contactNames()))
} }
contactsButton.setOnClickListener { contactsButton.setOnClickListener {
if (BaresipService.preferAndroidContacts) { val i = Intent(this@MainActivity, ContactsActivity::class.java)
val i = Intent(this@MainActivity, AndroidContactsActivity::class.java) val b = Bundle()
if (aorSpinner.selectedItemPosition >= 0) if (aorSpinner.selectedItemPosition >= 0)
i.putExtra("aor", aorSpinner.tag.toString()) b.putString("aor", aorSpinner.tag.toString())
else else
i.putExtra("aor", "") b.putString("aor", "")
startActivity(i) i.putExtras(b)
} else { contactsRequest.launch(i)
val i = Intent(this@MainActivity, ContactsActivity::class.java)
val b = Bundle()
if (aorSpinner.selectedItemPosition >= 0)
b.putString("aor", aorSpinner.tag.toString())
else
b.putString("aor", "")
i.putExtras(b)
contactsRequest.launch(i)
}
} }
chatRequests = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { chatRequests = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -932,6 +925,13 @@ class MainActivity : AppCompatActivity() {
if (event == "started") { if (event == "started") {
val callActionUri = params[0] val callActionUri = params[0]
Log.d(TAG, "Handling service event 'started' with '$callActionUri'") Log.d(TAG, "Handling service event 'started' with '$callActionUri'")
if (!this::uaAdapter.isInitialized) {
// Android has restarted baresip when permission has been denied in app settings
val i = Intent(this, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(i)
return
}
uaAdapter.notifyDataSetChanged() uaAdapter.notifyDataSetChanged()
if (callActionUri != "") { if (callActionUri != "") {
var ua = UserAgent.ofDomain(Utils.uriHostPart(callActionUri)) var ua = UserAgent.ofDomain(Utils.uriHostPart(callActionUri))
@ -1105,7 +1105,7 @@ class MainActivity : AppCompatActivity() {
val call = Call.ofCallp(callp)!! val call = Call.ofCallp(callp)!!
val titleView = View.inflate(this, R.layout.alert_title, null) as TextView val titleView = View.inflate(this, R.layout.alert_title, null) as TextView
titleView.text = getString(R.string.transfer_request) titleView.text = getString(R.string.transfer_request)
val target = Utils.friendlyUri(Utils.contactName(ev[1]), Utils.aorDomain(aor)) val target = Utils.friendlyUri(Contact.contactName(ev[1]), Utils.aorDomain(aor))
with(AlertDialog.Builder(this)) { with(AlertDialog.Builder(this)) {
setCustomTitle(titleView) setCustomTitle(titleView)
setMessage(String.format(getString(R.string.transfer_request_query), setMessage(String.format(getString(R.string.transfer_request_query),
@ -1403,7 +1403,7 @@ class MainActivity : AppCompatActivity() {
titleView.text = getString(R.string.call_transfer) titleView.text = getString(R.string.call_transfer)
val transferUri = layout.findViewById(R.id.transferUri) as AutoCompleteTextView val transferUri = layout.findViewById(R.id.transferUri) as AutoCompleteTextView
transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames())) Contact.contactNames()))
transferUri.threshold = 2 transferUri.threshold = 2
transferUri.requestFocus() transferUri.requestFocus()
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)
@ -1414,7 +1414,7 @@ class MainActivity : AppCompatActivity() {
dialog.dismiss() dialog.dismiss()
var uriText = transferUri.text.toString().trim() var uriText = transferUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
uriText = Utils.contactUri(uriText) ?: uriText uriText = Contact.contactUri(uriText) ?: uriText
if (Utils.isTelNumber(uriText)) if (Utils.isTelNumber(uriText))
uriText = "tel:$uriText" uriText = "tel:$uriText"
val uri = if (Utils.isTelUri(uriText)) val uri = if (Utils.isTelUri(uriText))
@ -1702,7 +1702,7 @@ class MainActivity : AppCompatActivity() {
if (Call.calls().isEmpty()) { if (Call.calls().isEmpty()) {
var uriText = callUri.text.toString().trim() var uriText = callUri.text.toString().trim()
if (uriText.isNotEmpty()) { if (uriText.isNotEmpty()) {
uriText = Utils.contactUri(uriText) ?: uriText uriText = Contact.contactUri(uriText) ?: uriText
if (Utils.isTelNumber(uriText)) if (Utils.isTelNumber(uriText))
uriText = "tel:$uriText" uriText = "tel:$uriText"
val uri = if (Utils.isTelUri(uriText)) { val uri = if (Utils.isTelUri(uriText)) {
@ -1750,10 +1750,8 @@ class MainActivity : AppCompatActivity() {
} else { } else {
val latest = NewCallHistory.aorLatestHistory(aor) val latest = NewCallHistory.aorLatestHistory(aor)
if (latest != null) if (latest != null)
callUri.setText( callUri.setText(Utils.friendlyUri(Contact.contactName(latest.peerUri),
Utils.friendlyUri(Utils.contactName(latest.peerUri), Utils.aorDomain(aor) Utils.aorDomain(aor)))
)
)
} }
} }
} }
@ -1772,7 +1770,7 @@ class MainActivity : AppCompatActivity() {
callUri.isFocusableInTouchMode = true callUri.isFocusableInTouchMode = true
imm.hideSoftInputFromWindow(callUri.windowToken, 0) imm.hideSoftInputFromWindow(callUri.windowToken, 0)
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item, callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames())) Contact.contactNames()))
securityButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE
callButton.visibility = View.VISIBLE callButton.visibility = View.VISIBLE
callButton.isEnabled = true callButton.isEnabled = true
@ -1797,7 +1795,7 @@ class MainActivity : AppCompatActivity() {
else else
getString(R.string.outgoing_call_to_dots) getString(R.string.outgoing_call_to_dots)
callTimer.visibility = View.INVISIBLE callTimer.visibility = View.INVISIBLE
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri), callUri.setText(Utils.friendlyUri(Contact.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor))) Utils.aorDomain(ua.account.aor)))
securityButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE
callButton.visibility = View.INVISIBLE callButton.visibility = View.INVISIBLE
@ -1812,7 +1810,7 @@ class MainActivity : AppCompatActivity() {
"incoming" -> { "incoming" -> {
callTitle.text = getString(R.string.incoming_call_from_dots) callTitle.text = getString(R.string.incoming_call_from_dots)
callTimer.visibility = View.INVISIBLE callTimer.visibility = View.INVISIBLE
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri), callUri.setText(Utils.friendlyUri(Contact.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor))) Utils.aorDomain(ua.account.aor)))
callUri.setAdapter(null) callUri.setAdapter(null)
securityButton.visibility = View.INVISIBLE securityButton.visibility = View.INVISIBLE
@ -1832,7 +1830,7 @@ class MainActivity : AppCompatActivity() {
} }
if (call.referTo != "") { if (call.referTo != "") {
callTitle.text = getString(R.string.transferring_call_to_dots) callTitle.text = getString(R.string.transferring_call_to_dots)
callUri.setText(Utils.friendlyUri(Utils.contactName(call.referTo), callUri.setText(Utils.friendlyUri(Contact.contactName(call.referTo),
Utils.aorDomain(ua.account.aor))) Utils.aorDomain(ua.account.aor)))
transferButton.isEnabled = false transferButton.isEnabled = false
} else { } else {
@ -1840,7 +1838,7 @@ class MainActivity : AppCompatActivity() {
callTitle.text = getString(R.string.outgoing_call_to_dots) callTitle.text = getString(R.string.outgoing_call_to_dots)
else else
callTitle.text = getString(R.string.incoming_call_from_dots) callTitle.text = getString(R.string.incoming_call_from_dots)
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri), callUri.setText(Utils.friendlyUri(Contact.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor))) Utils.aorDomain(ua.account.aor)))
transferButton.isEnabled = true transferButton.isEnabled = true
} }
@ -1973,21 +1971,6 @@ class MainActivity : AppCompatActivity() {
i.putExtras(b) i.putExtras(b)
startActivity(i) startActivity(i)
} }
"android contacts" -> {
val i = Intent(this, AndroidContactsActivity::class.java)
val b = Bundle()
b.putString("aor", activity[1])
i.putExtras(b)
startActivity(i)
}
"android contact" -> {
val i = Intent(this, AndroidContactActivity::class.java)
val b = Bundle()
b.putString("aor", activity[1])
b.putInt("index", activity[2].toInt())
i.putExtras(b)
startActivity(i)
}
"chats" -> { "chats" -> {
val i = Intent(this, ChatsActivity::class.java) val i = Intent(this, ChatsActivity::class.java)
val b = Bundle() val b = Bundle()

View File

@ -45,7 +45,7 @@ class MessageListAdapter(private val ctx: Context, private val rows: ArrayList<M
val peer: String = if (down) { val peer: String = if (down) {
lp.setMargins(0, 10, 75, 10) lp.setMargins(0, 10, 75, 10)
viewHolder.layoutView.setBackgroundResource(R.drawable.message_in_bg) viewHolder.layoutView.setBackgroundResource(R.drawable.message_in_bg)
val contactName = Utils.contactName(message.peerUri) val contactName = Contact.contactName(message.peerUri)
if (contactName.startsWith("sip:") && if (contactName.startsWith("sip:") &&
(Utils.uriHostPart(message.peerUri) == Utils.uriHostPart(message.aor))) (Utils.uriHostPart(message.peerUri) == Utils.uriHostPart(message.aor)))
Utils.uriUserPart(message.peerUri) Utils.uriUserPart(message.peerUri)

View File

@ -320,57 +320,43 @@ object Utils {
name.lines().size == 1 && !name.contains('"') name.lines().size == 1 && !name.contains('"')
} }
fun contactName(uri: String) : String {
return ContactsActivity.contactName(uri) ?: AndroidContactsActivity.contactName(uri)
}
fun contactUri(name: String) : String? {
return ContactsActivity.contactUri(name) ?: AndroidContactsActivity.contactUri(name)
}
fun reloadContactNames() {
BaresipService.contactNames.clear()
BaresipService.contactNames.addAll(Contact.contacts().map{Contact -> Contact.name})
BaresipService.contactNames.addAll(AndroidContact.contacts().map{AndroidContact -> AndroidContact.name})
BaresipService.contactNames.sort()
}
fun contactNames() : List<String> {
return BaresipService.contactNames
}
fun setAvatar(ctx: Context, imageView: ImageView, textView: TextView, uri: String) { fun setAvatar(ctx: Context, imageView: ImageView, textView: TextView, uri: String) {
val contact = ContactsActivity.findContact(uri)
if (contact != null) { when (val contact = Contact.findContact(uri)) {
val avatarImage = contact.avatarImage
if (avatarImage != null) { is Contact.BaresipContact -> {
imageView.setImageBitmap(avatarImage) val avatarImage = contact.avatarImage
} else { if (avatarImage != null) {
textView.background.setTint(contact.color) imageView.setImageBitmap(avatarImage)
if (contact.name.isNotEmpty())
textView.text = "${contact.name[0]}"
else
textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView))
}
} else {
val androidContact = AndroidContactsActivity.findContact(uri)
if (androidContact != null) {
val thumbnailUri = androidContact.thumbnailUri
if (thumbnailUri != null) {
imageView.setImageURI(thumbnailUri)
} else { } else {
textView.background.setTint(androidContact.color) textView.background.setTint(contact.color)
if (androidContact.name.isNotEmpty()) if (contact.name.isNotEmpty())
textView.text = "${androidContact.name[0]}" textView.text = "${contact.name[0]}"
else else
textView.text = "" textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView)) imageView.setImageBitmap(bitmapFromView(textView))
} }
} else { }
is Contact.AndroidContact -> {
val thumbnailUri = contact.thumbnailUri
if (thumbnailUri != null) {
imageView.setImageURI(thumbnailUri)
} else {
textView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
textView.text = "${contact.name[0]}"
else
textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView))
}
}
null -> {
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image) val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image)
imageView.setImageBitmap(bitmap) imageView.setImageBitmap(bitmap)
} }
} }
} }

View File

@ -215,37 +215,27 @@
android:id="@+id/AudioSettingsTitle" android:id="@+id/AudioSettingsTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="12dp"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:text="@string/audio_settings" > android:text="@string/audio_settings" >
</TextView> </TextView>
<RelativeLayout <TextView
android:id="@+id/ContactsTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp"
android:text="@string/contacts" >
</TextView>
<Spinner
android:id="@+id/contactsSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:orientation="horizontal" > android:paddingTop="8dp" >
<TextView </Spinner>
android:id="@+id/AndroidContactsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/AndroidContacts"
android:textSize="18sp"
android:text="@string/show_android_contacts" >
</TextView>
<CheckBox
android:id="@+id/AndroidContacts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_gravity="end"
android:checked="false" >
</CheckBox>
</RelativeLayout>
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -262,6 +262,7 @@
android:src="@drawable/voicemail" android:src="@drawable/voicemail"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:visibility="invisible"
android:contentDescription="@string/voicemail" > android:contentDescription="@string/voicemail" >
</ImageButton> </ImageButton>

View File

@ -329,9 +329,7 @@
<string name="video_size_help">Size of transmitted video frames (width x height). <string name="video_size_help">Size of transmitted video frames (width x height).
Factory default is 800x600.</string> Factory default is 800x600.</string>
<string name="android_settings">Android Settings</string> <string name="android_settings">Android Settings</string>
<string name="show_android_contacts">Show Android Contacts</string> <string name="contacts_help">Chooses if baresip contacts, Android contacts, or both will be used.</string>
<string name="show_android_contacts_help">If checked, Android contacts are shown when Contacts
button is touched.</string>
<string name="debug">Debug</string> <string name="debug">Debug</string>
<string name="debug_help">If checked, provides debug and info level log messages to Logcat.</string> <string name="debug_help">If checked, provides debug and info level log messages to Logcat.</string>
<string name="sip_trace">SIP Trace</string> <string name="sip_trace">SIP Trace</string>
@ -356,8 +354,7 @@
<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>
<string name="android" translatable="false">Android</string> <string name="android_contact_help">If checked, this contact is added to Android contacts.</string>
<string name="android_contact_help">If checked, this contact is available also in Android contacts.</string>
<string name="avatar_image">Profile image</string> <string name="avatar_image">Profile image</string>
<!-- Contacts Activity --> <!-- Contacts Activity -->
<string name="contacts">Contacts</string> <string name="contacts">Contacts</string>
@ -392,6 +389,9 @@
<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> <string name="invalid_sip_or_tel_uri">Invalid SIP or tel URI \'%1$s\'</string>
<string name="baresip" translatable="false">baresip</string>
<string name="android" translatable="false">Android</string>
<string name="both">Both</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>