Added possibility to use Android contacts

This commit is contained in:
Juha Heinanen
2022-02-13 12:51:59 +02:00
parent de673b105b
commit 21714a82c4
31 changed files with 267 additions and 208 deletions

View File

@ -1,8 +1,9 @@
package com.tutpro.baresip
import android.net.Uri
import java.util.ArrayList
class AndroidContact(val id: Long, var name: String, var color: Int, val thumbnailUri: String?) {
class AndroidContact(val id: Long, var name: String, var color: Int, var thumbnailUri: Uri?) {
val uris = ArrayList<String>()

View File

@ -1,21 +1,16 @@
package com.tutpro.baresip
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
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.ActivityAndroidContactBinding
class AndroidContactActivity : AppCompatActivity() {
@ -28,7 +23,6 @@ class AndroidContactActivity : AppCompatActivity() {
private lateinit var nameView: TextView
private lateinit var ulAdapter: AndroidUriListAdapter
private lateinit var aor: String
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var index = 0
private var color = 0
@ -49,7 +43,7 @@ class AndroidContactActivity : AppCompatActivity() {
cardImageAvatarView = binding.ImageAvatar
nameView = binding.Name
val contact = AndroidContactsActivity.androidContacts[index]
val contact = BaresipService.androidContacts[index]
val name = contact.name
color = contact.color
id = contact.id
@ -69,53 +63,6 @@ class AndroidContactActivity : AppCompatActivity() {
}
override fun onStart() {
super.onStart()
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
}
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) {
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 {
if (BaresipService.activities.indexOf("android contact,$aor,$index") == -1) return true
@ -151,10 +98,10 @@ class AndroidContactActivity : AppCompatActivity() {
textAvatarView.text = "${name[0]}"
}
private fun showImageAvatar(thumbNailUri: String) {
private fun showImageAvatar(thumbNailUri: Uri) {
textAvatarView.visibility = View.GONE
cardAvatarView.visibility = View.VISIBLE
cardImageAvatarView.setImageURI(thumbNailUri.toUri())
cardImageAvatarView.setImageURI(thumbNailUri)
}
}

View File

@ -11,7 +11,6 @@ import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity
import androidx.core.net.toUri
class AndroidContactListAdapter(private val ctx: Context,
private val rows: ArrayList<AndroidContact>,
@ -45,7 +44,7 @@ class AndroidContactListAdapter(private val ctx: Context,
val thumbNailUri = contact.thumbnailUri
if (thumbNailUri != null) {
viewHolder.imageAvatarView.setImageURI(thumbNailUri.toUri())
viewHolder.imageAvatarView.setImageURI(thumbNailUri)
} else {
contact.color = Utils.randomColor()
viewHolder.textAvatarView.background.setTint(contact.color)

View File

@ -1,35 +1,64 @@
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
getAndroidContacts(this)
clAdapter = AndroidContactListAdapter(this, androidContacts, aor)
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)
else
fetchAndroidContacts(this)
clAdapter = AndroidContactListAdapter(this, BaresipService.androidContacts, aor)
listView.adapter = clAdapter
Utils.addActivity("android contacts,$aor")
@ -45,7 +74,7 @@ class AndroidContactsActivity : AppCompatActivity() {
when (item.itemId) {
R.id.swapContactsIcon -> {
R.id.swapIcon -> {
if (SystemClock.elapsedRealtime() - lastSwap > 1000) {
lastSwap = SystemClock.elapsedRealtime()
BaresipService.activities.remove("android contacts,$aor")
@ -76,46 +105,110 @@ class AndroidContactsActivity : AppCompatActivity() {
}
private fun getAndroidContacts(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 + "'"
Log.d(TAG, "******* selection = /$selection/")
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) // display name
val mime = cur.getString(2) // type of data
val data = cur.getString(3) // data
val thumb = cur.getString(4) // thumbnail
// Log.d(TAG, "****** $id - $name - $mime - $data - $thumb")
val contact = if (contacts.containsKey(id))
contacts[id]!!
else
AndroidContact(id, name, 0, thumb)
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
contact.uris.add("tel:$data")
else if (mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
contact.uris.add("sip:$data")
else
continue
if (!contacts.containsKey(id))
contacts[id] = contact
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)
}
}
}
}
}
cur?.close()
androidContacts.clear()
for ((_, value) in contacts)
androidContacts.add(value)
return
}
companion object {
val androidContacts = ArrayList<AndroidContact>()
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, 0, 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)
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
}
}
}

View File

@ -296,6 +296,7 @@ class BaresipService: Service() {
this.registerReceiver(bluetoothReceiver, filter)
}
AndroidContactsActivity.fetchAndroidContacts(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -648,8 +649,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val caller = Utils.friendlyUri(ContactsActivity.contactName(peerUri),
Utils.aorDomain(aor))
val caller = Utils.friendlyUri(Utils.contactName(peerUri), Utils.aorDomain(aor))
nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -743,8 +743,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, TRANSFER_REQ_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]),
Utils.aorDomain(aor))
val target = Utils.friendlyUri(Utils.contactName(ev[1]), Utils.aorDomain(aor))
nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -820,8 +819,7 @@ class BaresipService: Service() {
}
if (!Utils.isVisible()) {
if (missed) {
val caller = Utils.friendlyUri(ContactsActivity.contactName(call.peerUri),
Utils.aorDomain(aor))
val caller = Utils.friendlyUri(Utils.contactName(call.peerUri), Utils.aorDomain(aor))
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
@ -927,8 +925,7 @@ class BaresipService: Service() {
PendingIntent.getActivity(applicationContext, MESSAGE_REQ_CODE, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val sender = Utils.friendlyUri(ContactsActivity.contactName(peer),
Utils.aorDomain(ua.account.aor))
val sender = Utils.friendlyUri(Utils.contactName(peer), Utils.aorDomain(ua.account.aor))
nb.setSmallIcon(R.drawable.ic_stat_message)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -1111,8 +1108,12 @@ class BaresipService: Service() {
}
private fun requestAudioFocus(type: Int) {
if (audioFocusRequest != null && audioFocusRequest!!.audioAttributesCompat.contentType == type)
return
if (audioFocusRequest != null) {
if (audioFocusRequest!!.audioAttributesCompat.contentType == type)
return
else
abandonAudioFocus()
}
val attributes = AudioAttributesCompat.Builder()
.setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
.setContentType(type)
@ -1418,6 +1419,7 @@ class BaresipService: Service() {
val messageUpdate = MutableLiveData<Long>()
val registrationUpdate = MutableLiveData<Long>()
val contacts = ArrayList<Contact>()
val androidContacts = ArrayList<AndroidContact>()
val chatTexts: MutableMap<String, String> = mutableMapOf()
val activities = mutableListOf<String>()
var dnsServers = listOf<InetAddress>()

View File

@ -2,7 +2,6 @@ package com.tutpro.baresip
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -12,8 +11,6 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import java.util.*
class CallListAdapter(private val ctx: Context, private val aor: String, private val rows: ArrayList<CallRow>) :
ArrayAdapter<CallRow>(ctx, R.layout.call_row, rows) {
@ -44,23 +41,7 @@ class CallListAdapter(private val ctx: Context, private val aor: String, private
val callRow = rows[position]
val contact = ContactsActivity.findContact(callRow.peerUri)
if (contact != null) {
val avatarImage = contact.avatarImage
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))
}
} else {
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image)
viewHolder.imageAvatarView.setImageBitmap(bitmap)
}
Utils.setAvatar(ctx, viewHolder.imageAvatarView, viewHolder.textAvatarView, callRow.peerUri)
viewHolder.directionsView.removeAllViews()
var count = 1
@ -81,7 +62,7 @@ class CallListAdapter(private val ctx: Context, private val aor: String, private
if (count <= 3)
viewHolder.etcView.text = ""
val contactName = ContactsActivity.contactName(callRow.peerUri)
val contactName = Utils.contactName(callRow.peerUri)
if (contactName.startsWith("sip:"))
viewHolder.peerURIView.text = Utils.friendlyUri(contactName, Utils.aorDomain(callRow.aor))
else

View File

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

View File

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

View File

@ -1,17 +1,18 @@
package com.tutpro.baresip
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Typeface
import androidx.core.content.ContextCompat
import android.text.format.DateUtils.isToday
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import java.util.*
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import java.text.DateFormat
import java.util.*
class ChatListAdapter(private val ctx: Context, private val rows: ArrayList<Message>) :
ArrayAdapter<Message>(ctx, R.layout.message, rows) {
@ -43,26 +44,7 @@ class ChatListAdapter(private val ctx: Context, private val rows: ArrayList<Mess
val message = rows[position]
val contact = ContactsActivity.findContact(message.peerUri)
val peer: String
if (contact != null) {
peer = contact.name
val avatarImage = contact.avatarImage
if (avatarImage != null) {
viewHolder.imageAvatarView.setImageBitmap(avatarImage)
} else {
viewHolder.textAvatarView.background.setTint(contact.color)
if (peer.isNotEmpty())
viewHolder.textAvatarView.text = "${peer[0]}"
else
viewHolder.textAvatarView.text = ""
viewHolder.imageAvatarView.setImageBitmap(Utils.bitmapFromView(viewHolder.textAvatarView))
}
} else {
peer = Utils.friendlyUri(message.peerUri, message.aor)
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image)
viewHolder.imageAvatarView.setImageBitmap(bitmap)
}
Utils.setAvatar(ctx, viewHolder.imageAvatarView, viewHolder.textAvatarView, message.peerUri)
if ((message.direction == R.drawable.arrow_down_green) ||
(message.direction == R.drawable.arrow_down_red))
@ -70,7 +52,11 @@ class ChatListAdapter(private val ctx: Context, private val rows: ArrayList<Mess
else
viewHolder.layoutView.setBackgroundResource(R.drawable.message_out_bg)
viewHolder.peerView.text = peer
val peerName = Utils.contactName(message.peerUri)
viewHolder.peerView.text = if (peerName !=message.peerUri )
peerName
else
Utils.friendlyUri(message.peerUri, message.aor)
val cal = GregorianCalendar()
cal.timeInMillis = message.timeStamp

View File

@ -95,7 +95,7 @@ class ChatsActivity: AppCompatActivity() {
}
val builder = AlertDialog.Builder(this@ChatsActivity, R.style.Theme_AppCompat)
val peer = ContactsActivity.contactName(uaMessages[pos].peerUri)
val peer = Utils.contactName(uaMessages[pos].peerUri)
if (peer.startsWith("sip:"))
with (builder) {
setMessage(String.format(getString(R.string.long_chat_question),

View File

@ -8,10 +8,8 @@ import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -528,13 +526,6 @@ class ConfigActivity : AppCompatActivity() {
binding.AudioSettingsTitle.setOnClickListener {
startActivity(Intent(this, AudioActivity::class.java))
}
binding.AndroidSettings.setOnClickListener {
val i = Intent()
i.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
i.data = Uri.parse("package:${applicationContext.packageName}")
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
startActivity(i)
}
binding.DarkThemeTitle.setOnClickListener {
Utils.alertView(this, getString(R.string.dark_theme),
getString(R.string.dark_theme_help))

View File

@ -33,7 +33,6 @@ import com.tutpro.baresip.databinding.ActivityContactBinding
import java.io.ByteArrayOutputStream
import java.io.File
class ContactActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactBinding

View File

@ -74,7 +74,7 @@ class ContactsActivity : AppCompatActivity() {
when (item.itemId) {
R.id.swapContactsIcon -> {
R.id.swapIcon -> {
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
BaresipService.activities.remove("contacts,$aor")
@ -117,9 +117,10 @@ class ContactsActivity : AppCompatActivity() {
}
fun findContact(uri: String): Contact? {
for (c in Contact.contacts())
for (c in Contact.contacts()) {
if (Utils.uriMatch(c.uri, uri))
return c
}
return null
}

View File

@ -1105,8 +1105,7 @@ class MainActivity : AppCompatActivity() {
val call = Call.ofCallp(callp)!!
val titleView = View.inflate(this, R.layout.alert_title, null) as TextView
titleView.text = getString(R.string.transfer_request)
val target = Utils.friendlyUri(ContactsActivity.contactName(ev[1]),
Utils.aorDomain(aor))
val target = Utils.friendlyUri(Utils.contactName(ev[1]), Utils.aorDomain(aor))
with(AlertDialog.Builder(this)) {
setCustomTitle(titleView)
setMessage(String.format(getString(R.string.transfer_request_query),
@ -1752,8 +1751,7 @@ class MainActivity : AppCompatActivity() {
val latest = NewCallHistory.aorLatestHistory(aor)
if (latest != null)
callUri.setText(
Utils.friendlyUri(
ContactsActivity.contactName(latest.peerUri), Utils.aorDomain(aor)
Utils.friendlyUri(Utils.contactName(latest.peerUri), Utils.aorDomain(aor)
)
)
}
@ -1799,7 +1797,7 @@ class MainActivity : AppCompatActivity() {
else
getString(R.string.outgoing_call_to_dots)
callTimer.visibility = View.INVISIBLE
callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.peerUri),
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor)))
securityButton.visibility = View.INVISIBLE
callButton.visibility = View.INVISIBLE
@ -1814,7 +1812,7 @@ class MainActivity : AppCompatActivity() {
"incoming" -> {
callTitle.text = getString(R.string.incoming_call_from_dots)
callTimer.visibility = View.INVISIBLE
callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.peerUri),
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor)))
callUri.setAdapter(null)
securityButton.visibility = View.INVISIBLE
@ -1834,7 +1832,7 @@ class MainActivity : AppCompatActivity() {
}
if (call.referTo != "") {
callTitle.text = getString(R.string.transferring_call_to_dots)
callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.referTo),
callUri.setText(Utils.friendlyUri(Utils.contactName(call.referTo),
Utils.aorDomain(ua.account.aor)))
transferButton.isEnabled = false
} else {
@ -1842,7 +1840,7 @@ class MainActivity : AppCompatActivity() {
callTitle.text = getString(R.string.outgoing_call_to_dots)
else
callTitle.text = getString(R.string.incoming_call_from_dots)
callUri.setText(Utils.friendlyUri(ContactsActivity.contactName(call.peerUri),
callUri.setText(Utils.friendlyUri(Utils.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor)))
transferButton.isEnabled = true
}

View File

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

View File

@ -7,6 +7,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Bitmap.createScaledBitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.net.*
@ -22,6 +23,7 @@ import android.text.TextWatcher
import android.text.format.DateUtils
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
@ -107,7 +109,7 @@ object Utils {
fun uriMatch(firstUri: String, secondUri: String): Boolean {
if (firstUri.startsWith("tel:"))
return firstUri == secondUri
return firstUri == secondUri || firstUri.substringAfter(":") == uriUserPart(secondUri)
if (firstUri.startsWith("sip:"))
return uriUserPart(firstUri) == uriUserPart(secondUri) &&
uriHostPart(firstUri) == uriHostPart(secondUri)
@ -318,6 +320,49 @@ object Utils {
name.lines().size == 1 && !name.contains('"')
}
fun contactName(uri: String) : String {
val name = ContactsActivity.contactName(uri)
return if (name != uri)
name
else
AndroidContactsActivity.contactName(uri)
}
fun setAvatar(ctx: Context, imageView: ImageView, textView: TextView, uri: String) {
val contact = ContactsActivity.findContact(uri)
if (contact != null) {
val avatarImage = contact.avatarImage
if (avatarImage != null) {
imageView.setImageBitmap(avatarImage)
} else {
textView.background.setTint(contact.color)
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 {
textView.background.setTint(androidContact.color)
if (androidContact.name.isNotEmpty())
textView.text = "${androidContact.name[0]}"
else
textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView))
}
} else {
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image)
imageView.setImageBitmap(bitmap)
}
}
}
@Suppress("unused")
fun checkIfName(name: String): Boolean {
if ((name.length < 2) || !name.first().isLetter()) return false

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M6.54,5c0.06,0.89 0.21,1.76 0.45,2.59l-1.2,1.2c-0.41,-1.2 -0.67,-2.47 -0.76,-3.79h1.51m9.86,12.02c0.85,0.24 1.72,0.39 2.6,0.45v1.49c-1.32,-0.09 -2.59,-0.35 -3.8,-0.75l1.2,-1.19M7.5,3H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.49c0,-0.55 -0.45,-1 -1,-1 -1.24,0 -2.45,-0.2 -3.57,-0.57 -0.1,-0.04 -0.21,-0.05 -0.31,-0.05 -0.26,0 -0.51,0.1 -0.71,0.29l-2.2,2.2c-2.83,-1.45 -5.15,-3.76 -6.59,-6.59l2.2,-2.2c0.28,-0.28 0.36,-0.67 0.25,-1.02C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1z"/>
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M21,5c-1.11,-0.35 -2.33,-0.5 -3.5,-0.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.25 0.25,0.5 0.5,0.5c0.1,0 0.15,-0.05 0.25,-0.05C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05c0.1,0.05 0.15,0.05 0.25,0.05c0.25,0 0.5,-0.25 0.5,-0.5V6C22.4,5.55 21.75,5.25 21,5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,10.5c0.88,0 1.73,0.09 2.5,0.26V9.24C19.21,9.09 18.36,9 17.5,9c-1.7,0 -3.24,0.29 -4.5,0.83v1.66C14.13,10.85 15.7,10.5 17.5,10.5z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M13,12.49v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26V11.9c-0.79,-0.15 -1.64,-0.24 -2.5,-0.24C15.8,11.66 14.26,11.96 13,12.49z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,14.33c-1.7,0 -3.24,0.29 -4.5,0.83v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26v-1.52C19.21,14.41 18.36,14.33 17.5,14.33z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:tint="@android:color/white" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000" android:pathData="M20,15.5C18.8,15.5 17.5,15.3 16.4,14.9C16.3,14.9 16.2,14.9 16.1,14.9C15.8,14.9 15.6,15 15.4,15.2L13.2,17.4C10.4,15.9 8,13.6 6.6,10.8L8.8,8.6C9.1,8.3 9.2,7.9 9,7.6C8.7,6.5 8.5,5.2 8.5,4C8.5,3.5 8,3 7.5,3H4C3.5,3 3,3.5 3,4C3,13.4 10.6,21 20,21C20.5,21 21,20.5 21,20V16.5C21,16 20.5,15.5 20,15.5M5,5H6.5C6.6,5.9 6.8,6.8 7,7.6L5.8,8.8C5.4,7.6 5.1,6.3 5,5M19,19C17.7,18.9 16.4,18.6 15.2,18.2L16.4,17C17.2,17.2 18.1,17.4 19,17.4V19Z"/>
<path android:fillColor="#000000" android:pathData="M20,15.5C18.8,15.5 17.5,15.3 16.4,14.9C16.3,14.9 16.2,14.9 16.1,14.9C15.8,14.9 15.6,15 15.4,15.2L13.2,17.4C10.4,15.9 8,13.6 6.6,10.8L8.8,8.6C9.1,8.3 9.2,7.9 9,7.6C8.7,6.5 8.5,5.2 8.5,4C8.5,3.5 8,3 7.5,3H4C3.5,3 3,3.5 3,4C3,13.4 10.6,21 20,21C20.5,21 21,20.5 21,20V16.5C21,16 20.5,15.5 20,15.5M5,5H6.5C6.6,5.9 6.8,6.8 7,7.6L5.8,8.8C5.4,7.6 5.1,6.3 5,5M19,19C17.7,18.9 16.4,18.6 15.2,18.2L16.4,17C17.2,17.2 18.1,17.4 19,17.4V19Z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:autoMirrored="true" android:height="48dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:viewportWidth="24" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.5,4.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.65 0.73,0.45 0.75,0.45C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05C22.66,21.26 23,20.86 23,20.6V6C21.51,4.88 19.37,4.5 17.5,4.5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z"/>
</vector>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/AndroidContactsView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"

View File

@ -221,16 +221,6 @@
android:text="@string/audio_settings" >
</TextView>
<TextView
android:id="@+id/AndroidSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@string/android_settings"
android:textStyle="bold"
android:textSize="18sp">
</TextView>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -52,7 +52,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/aorSpinner"
android:layout_marginTop="31dp"
android:layout_marginTop="12dp"
android:orientation="horizontal" >
<TextView

View File

@ -3,9 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/swapContactsIcon"
android:id="@+id/swapIcon"
android:title=""
app:showAsAction="always"
android:icon="@drawable/swap_contacts" />
android:icon="@drawable/ic_test_swap" />
</menu>

View File

@ -520,7 +520,7 @@
Tallennustila-käyttöoikeutta.</string>
<string name="no_restore">Et voi palauttaa sovelluksen tietoja ilman
Tallennustila-käyttöoikeutta.</string>
<string name="no_android_contacts">Et voi lisätä tai poistaa Android-yhteystietoja ilman
<string name="no_android_contacts">Et voi käsitellä Androidin yhteystietoja ilman
Kontaktit-käyttöoikeutta.</string>
<string name="no_cameras">Sinulla ei ole yhtään tuettua video-kameraa.</string>
<string name="show_password">Näytä salasana</string>

View File

@ -475,7 +475,7 @@
<string name="no_video_calls">Grant \"Camera\" permission to make or answer video calls.</string>
<string name="no_backup">You are not able create backup without \"Storage\" permission.</string>
<string name="no_restore">You are not able restore backup without \"Storage\" permission.</string>
<string name="no_android_contacts">You are not able add or remove Android contacts without
<string name="no_android_contacts">You are not able to access Android contacts without
\"Contacts\" permission.</string>
<string name="no_cameras">You don\'t have any supported video cameras.</string>
<string name="show_password">Show Password</string>