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
android:name=".ContactsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/baresip_contacts"
android:label="@string/contacts"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustPan" >
</activity>
@ -95,18 +95,11 @@
android:label="@string/contact"
android:parentActivityName=".ContactsActivity" >
</activity>
<activity
android:name=".AndroidContactsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/android_contacts"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustPan" >
</activity>
<activity
android:name=".AndroidContactActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/contact"
android:parentActivityName=".AndroidContactsActivity" >
android:parentActivityName=".ContactsActivity" >
</activity>
<activity
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 color = 0
private var id: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
@ -43,10 +42,9 @@ class AndroidContactActivity : AppCompatActivity() {
cardImageAvatarView = binding.ImageAvatar
nameView = binding.Name
val contact = BaresipService.androidContacts[index]
val contact = Contact.contacts()[index] as Contact.AndroidContact
val name = contact.name
color = contact.color
id = contact.id
val thumbnailUri = contact.thumbnailUri
if (thumbnailUri != null)
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.nio.charset.StandardCharsets
import java.util.*
import kotlin.collections.ArrayList
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
@ -74,6 +75,7 @@ class BaresipService: Service() {
private var hotSpotIsEnabled = false
private var hotSpotAddresses = mapOf<String, String>()
private var mediaPlayer: MediaPlayer? = null
private var contentObserverRegistered = false
@SuppressLint("WakelockTimeout")
override fun onCreate() {
@ -299,33 +301,28 @@ class BaresipService: Service() {
this.registerReceiver(bluetoothReceiver, filter)
}
val contentObserver: ContentObserver = object : ContentObserver(null) {
contentObserver = object : ContentObserver(null) {
override fun onChange(self: Boolean) {
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 {
val action: String
val action: String?
if (intent == null) {
action = "Start"
Log.d(TAG, "Received onStartCommand with null intent")
} else {
// Utils.dumpIntent(intent)
action = intent.action!!
action = intent.action
Log.d(TAG, "Received onStartCommand action $action")
}
@ -359,13 +356,21 @@ class BaresipService: Service() {
Log.i(TAG, "Asset '$a' already copied")
}
if (a == "config")
Config.initialize()
Config.initialize(applicationContext)
}
if (File(filesDir, "history").exists())
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()
if (history.isEmpty()) {
NewCallHistory.restore()
@ -417,6 +422,14 @@ class BaresipService: Service() {
}
"Start Content Observer" -> {
registerContentObserver()
}
"Stop Content Observer" -> {
unRegisterContentObserver()
}
"Call Answer" -> {
val uap = intent!!.getStringExtra("uap")!!
val callp = intent.getStringExtra("callp")!!
@ -666,7 +679,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(Utils.contactName(peerUri), Utils.aorDomain(aor))
val caller = Utils.friendlyUri(Contact.contactName(peerUri), Utils.aorDomain(aor))
nb.setSmallIcon(R.drawable.ic_stat_call)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -760,7 +773,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(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)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -836,7 +849,7 @@ class BaresipService: Service() {
}
if (!Utils.isVisible()) {
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)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or
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.FLAG_UPDATE_CURRENT)
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)
.setColor(ContextCompat.getColor(this, R.color.colorBaresip))
.setContentIntent(pi)
@ -1389,6 +1402,24 @@ class BaresipService: Service() {
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() {
am.mode = AudioManager.MODE_NORMAL
abandonAudioFocus()
@ -1429,7 +1460,6 @@ class BaresipService: Service() {
var callActionUri = ""
var isMainVisible = false
var isMicMuted = false
var preferAndroidContacts = false
val uas = ArrayList<UserAgent>()
val status = ArrayList<Int>()
@ -1437,10 +1467,13 @@ class BaresipService: Service() {
var callHistory = ArrayList<NewCallHistory>()
var messages = ArrayList<Message>()
val messageUpdate = MutableLiveData<Long>()
val contactUpdate = MutableLiveData<Long>()
val registrationUpdate = MutableLiveData<Long>()
val baresipContacts = ArrayList<Contact.BaresipContact>()
val androidContacts = ArrayList<Contact.AndroidContact>()
val contacts = ArrayList<Contact>()
val androidContacts = ArrayList<AndroidContact>()
val contactNames = mutableListOf<String>()
val contactNames = ArrayList<String>()
var contactsMode = "baresip"
val chatTexts: MutableMap<String, String> = mutableMapOf()
val activities = mutableListOf<String>()
var dnsServers = listOf<InetAddress>()

View File

@ -62,7 +62,7 @@ class CallListAdapter(private val ctx: Context, private val aor: String, private
if (count <= 3)
viewHolder.etcView.text = ""
val contactName = Utils.contactName(callRow.peerUri)
val contactName = Contact.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 = Utils.contactName(peerUri)
var peerName = Contact.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 = Utils.contactName(peerUri)
val peerName = Contact.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 = Utils.contactName(peerUri)
var chatPeer = Contact.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 (Utils.contactName(peerUri) == peerUri)
if (Contact.contactName(peerUri) == peerUri)
with (builder) {
setMessage(String.format(getString(R.string.long_message_question),
chatPeer))

View File

@ -52,7 +52,7 @@ class ChatListAdapter(private val ctx: Context, private val rows: ArrayList<Mess
else
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 )
peerName
else

View File

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

View File

@ -1,5 +1,6 @@
package com.tutpro.baresip
import android.Manifest
import android.content.Context
import java.net.InetAddress
import java.nio.charset.StandardCharsets
@ -9,7 +10,7 @@ object Config {
private val configPath = BaresipService.filesPath + "/config"
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")
@ -85,7 +86,19 @@ object Config {
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())
BaresipService.isConfigInitialized = true

View File

@ -32,6 +32,7 @@ class ConfigActivity : AppCompatActivity() {
private lateinit var binding: ActivityConfigBinding
private lateinit var layout: ScrollView
private lateinit var baresipService: Intent
private lateinit var autoStart: CheckBox
private lateinit var batteryOptimizations: CheckBox
private lateinit var listenAddr: EditText
@ -40,11 +41,14 @@ class ConfigActivity : AppCompatActivity() {
private lateinit var verifyServer: CheckBox
private lateinit var caFile: 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 sipTrace: CheckBox
private lateinit var reset: CheckBox
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var oldAutoStart = ""
private var oldListenAddr = ""
@ -54,7 +58,7 @@ class ConfigActivity : AppCompatActivity() {
private var oldCAFile = false
private var oldLogLevel = ""
private var oldDisplayTheme = -1
private var oldAndroidContacts = ""
private var oldContactsMode = ""
private var save = false
private var restart = false
private var menu: Menu? = null
@ -63,12 +67,16 @@ class ConfigActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
val contactsPermissions = arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
binding = ActivityConfigBinding.inflate(layoutInflater)
setContentView(binding.root)
layout = binding.ConfigView
Utils.addActivity("config")
baresipService = Intent(this@ConfigActivity, BaresipService::class.java)
autoStart = binding.AutoStart
val asCv = Config.variable("auto_start")
oldAutoStart = if (asCv.size == 0) "no" else asCv[0]
@ -291,10 +299,27 @@ class ConfigActivity : AppCompatActivity() {
oldDisplayTheme = Preferences(applicationContext).displayTheme
darkTheme.isChecked = oldDisplayTheme == AppCompatDelegate.MODE_NIGHT_YES
androidContacts = binding.AndroidContacts
val acCv = Config.variable("prefer_android_contacts")
oldAndroidContacts = if (acCv.size == 0) "no" else acCv[0]
androidContacts.isChecked = oldAndroidContacts == "yes"
contactsModes = arrayListOf(
getString(R.string.baresip), getString(R.string.android), getString(R.string.both))
contactsSpinner = binding.contactsSpinner
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
val dbCv = Config.variable("log_level")
@ -340,7 +365,14 @@ class ConfigActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
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 {
@ -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 {
if (BaresipService.activities.indexOf("config") == -1) return true
@ -433,13 +503,28 @@ class ConfigActivity : AppCompatActivity() {
if (oldDisplayTheme != newDisplayTheme)
Preferences(applicationContext).displayTheme = newDisplayTheme
val androidContactsString = if (androidContacts.isChecked)
"yes"
else
"no"
if (oldAndroidContacts != androidContactsString) {
Config.replaceVariable("prefer_android_contacts", androidContactsString)
BaresipService.preferAndroidContacts = androidContacts.isChecked
if (oldContactsMode != contactsMode) {
Config.replaceVariable("contacts_mode", contactsMode)
BaresipService.contactsMode = contactsMode
when (contactsMode) {
"baresip" -> {
BaresipService.androidContacts.clear()
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
}
@ -530,9 +615,9 @@ class ConfigActivity : AppCompatActivity() {
Utils.alertView(this, getString(R.string.dark_theme),
getString(R.string.dark_theme_help))
}
binding.AndroidContactsTitle.setOnClickListener {
Utils.alertView(this, getString(R.string.show_android_contacts),
getString(R.string.show_android_contacts_help))
binding.ContactsTitle.setOnClickListener {
Utils.alertView(this, getString(R.string.contacts),
getString(R.string.contacts_help))
}
binding.DebugTitle.setOnClickListener {
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 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 CALL_NOTIFICATION_ID = 102

View File

@ -1,39 +1,165 @@
package com.tutpro.baresip
import android.content.Context
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.ContactsContract
import androidx.core.net.toUri
import java.io.File
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
var androidContact = false
class BaresipContact(var name: String, var uri: String, var color: Int, val id: Long): Contact() {
var avatarImage: Bitmap? = null
}
class AndroidContact(var name: String, var color: Int, var thumbnailUri: Uri?): Contact() {
val uris = ArrayList<String>()
}
companion object {
const val CONTACTS_SIZE = 100
const val CONTACTS_SIZE = 256
fun contacts(): ArrayList<Contact> {
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 = ""
for (c in BaresipService.contacts) contents +=
"\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color};android=${c.androidContact}\n"
return Utils.putFileContents(BaresipService.filesPath + "/contacts",
for (c in BaresipService.contacts)
if (c is BaresipContact)
contents += "\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color}\n"
Utils.putFileContents(BaresipService.filesPath + "/contacts",
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")
?: return false
val contacts = String(content)
BaresipService.contacts.clear()
var contactNo = 0
val baseId = System.currentTimeMillis()
BaresipService.baresipContacts.clear()
contacts.lines().forEach {
val parts = it.split("\"")
if (parts.size == 3) {
@ -47,15 +173,13 @@ class Contact(var name: String, var uri: String, var color: Int, val id: Long) {
colorValue.toInt()
else
Utils.randomColor()
val androidContact = Utils.paramValue(params, "android" ) == "true"
val idValue = Utils.paramValue(params, "id" )
val id: Long = if (idValue != "")
idValue.toLong()
else
baseId + contactNo
Log.d(TAG, "Restoring contact $name, $uri, $color, $id, $androidContact")
val contact = Contact(name, uri, color, id)
contact.androidContact = androidContact
Log.d(TAG, "Restoring contact $name, $uri, $color, $id")
val contact = BaresipContact(name, uri, color, id)
val avatarFilePath = BaresipService.filesPath + "/$id.png"
if (File(avatarFilePath).exists()) {
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")
}
}
BaresipService.contacts.add(contact)
BaresipService.baresipContacts.add(contact)
}
}
Utils.reloadContactNames()
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
import android.Manifest
import android.app.Activity
import android.content.ContentProviderOperation
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.Bundle
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
@ -21,14 +18,10 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
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 java.io.ByteArrayOutputStream
import java.io.File
@ -44,7 +37,6 @@ class ContactActivity : AppCompatActivity() {
private lateinit var uriView: EditText
private lateinit var androidCheck: CheckBox
private lateinit var menu: Menu
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private var newContact = false
private var newAvatar = ""
@ -53,10 +45,6 @@ class ContactActivity : AppCompatActivity() {
private var index = 0
private var color = 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?) {
@ -75,6 +63,10 @@ class ContactActivity : AppCompatActivity() {
newContact = intent.getBooleanExtra("new", false)
if (newContact) {
if (BaresipService.contactsMode == "baresip") {
binding.AndroidTitle.visibility = View.GONE
androidCheck.visibility = View.GONE
}
title = getString(R.string.new_contact)
color = Utils.randomColor()
id = System.currentTimeMillis()
@ -88,27 +80,33 @@ class ContactActivity : AppCompatActivity() {
else
uriView.setText(uri)
uOrI = uri
androidCheck.isChecked = false
if ((BaresipService.contactsMode == "Android")) {
androidCheck.isChecked = true
androidCheck.isClickable = false
} else {
androidCheck.isChecked = false
}
} else {
binding.AndroidTitle.visibility = View.GONE
androidCheck.visibility = View.GONE
index = intent.getIntExtra("index", 0)
val contact = Contact.contacts()[index]
val name = contact.name
color = contact.color
id = contact.id
val avatarImage = contact.avatarImage
if (avatarImage != null)
showImageAvatar(avatarImage)
else
showTextAvatar(name, color)
title = name
nameView.setText(name)
uriView.setText(contact.uri)
uOrI = index.toString()
androidCheck.isChecked = contact.androidContact
if (contact is Contact.BaresipContact) {
val name = contact.name
color = contact.color
id = contact.id
val avatarImage = contact.avatarImage
if (avatarImage != null)
showImageAvatar(avatarImage)
else
showTextAvatar(name, color)
title = name
nameView.setText(name)
uriView.setText(contact.uri)
uOrI = index.toString()
}
}
oldAndroid = androidCheck.isChecked
val avatarRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
@ -175,12 +173,6 @@ class ContactActivity : AppCompatActivity() {
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 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 {
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 {
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))
return false
}
val alert: Boolean = if (newContact)
ContactsActivity.nameExists(newName, true)
else
(Contact.contacts()[index].name != newName) &&
ContactsActivity.nameExists(newName, false)
Contact.nameExists(newName, true)
else {
val c = Contact.contacts()[index] as Contact.BaresipContact
(c.name != newName) && Contact.nameExists(newName, false)
}
if (alert) {
Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.contact_already_exists), newName))
@ -287,7 +233,8 @@ class ContactActivity : AppCompatActivity() {
return false
}
val contact: Contact
val contact: Contact.BaresipContact
if (newContact) {
if (Contact.contacts().size >= Contact.CONTACTS_SIZE) {
Utils.alertView(this, getString(R.string.notice),
@ -296,16 +243,13 @@ class ContactActivity : AppCompatActivity() {
BaresipService.activities.removeAt(0)
return true
} else {
contact = Contact(newName, newUri, color, id)
contact.androidContact = androidCheck.isChecked
Contact.contacts().add(contact)
contact = Contact.BaresipContact(newName, newUri, color, id)
}
} else {
contact = Contact.contacts()[index]
contact = Contact.contacts()[index] as Contact.BaresipContact
contact.uri = newUri
contact.name = newName
contact.color = color
contact.androidContact = androidCheck.isChecked
}
when (newAvatar) {
@ -323,21 +267,18 @@ class ContactActivity : AppCompatActivity() {
}
}
Contact.contacts().sortBy { Contact -> Contact.name }
if (Utils.checkPermissions(this, permissions)) {
if (contact.androidContact)
addOrUpdateAndroidContact(this, contact)
else if (oldAndroid)
deleteAndroidContact(this, contact)
if (androidCheck.isChecked) {
addOrUpdateAndroidContact(this, contact)
} 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")
val i = Intent(this, MainActivity::class.java)
i.putExtra("name", newName)
setResult(Activity.RESULT_OK, i)
@ -407,7 +348,7 @@ class ContactActivity : AppCompatActivity() {
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 selection = ContactsContract.Data.MIMETYPE + "='" +
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
@ -420,10 +361,9 @@ class ContactActivity : AppCompatActivity() {
addAndroidContact(ctx, contact)
}
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>()
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
@ -464,7 +404,7 @@ class ContactActivity : AppCompatActivity() {
return true
}
private fun updateAndroidContact(rawContactId: Long, contact: Contact) {
private fun updateAndroidContact(rawContactId: Long, contact: Contact.BaresipContact) {
if (updateAndroidUri(rawContactId, contact.uri) == 0)
addAndroidUri(rawContactId, contact.uri)
if (updateAndroidPhoto(rawContactId, contact.avatarImage) == 0)
@ -561,9 +501,9 @@ class ContactActivity : AppCompatActivity() {
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,
ContactsContract.Contacts.DISPLAY_NAME + "='" + contact.name + "'",
ContactsContract.Contacts.DISPLAY_NAME + "='" + name + "'",
null)
}

View File

@ -1,22 +1,22 @@
package com.tutpro.baresip
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.SystemClock
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
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 java.io.File
import java.io.IOException
import java.util.*
class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<Contact>,
private val aor: String) :
@ -48,82 +48,137 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
val contact = rows[position]
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))
}
if (contact is Contact.BaresipContact) {
viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f
viewHolder.nameView.setPadding(6, 6, 0, 6)
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))
}
if (aor != "") {
viewHolder.nameView.setOnClickListener {
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE, DialogInterface.BUTTON_NEGATIVE -> {
val i = Intent(ctx, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
if (which == DialogInterface.BUTTON_NEGATIVE)
i.putExtra("action", "call")
else
i.putExtra("action", "message")
val ua = UserAgent.ofAor(aor)
if (ua == null) {
Log.w(TAG, "onClickListener did not find AoR $aor")
} else {
BaresipService.activities.clear()
i.putExtra("uap", ua.uap)
i.putExtra("peer", contact.uri)
(ctx as Activity).startActivity(i)
viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f
viewHolder.nameView.setPadding(6, 6, 0, 6)
if (aor != "") {
viewHolder.nameView.setOnClickListener {
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE, DialogInterface.BUTTON_NEGATIVE -> {
val i = Intent(ctx, MainActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
if (which == DialogInterface.BUTTON_NEGATIVE)
i.putExtra("action", "call")
else
i.putExtra("action", "message")
val ua = UserAgent.ofAor(aor)
if (ua == null) {
Log.w(TAG, "onClickListener did not find AoR $aor")
} 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) {
lastClick = SystemClock.elapsedRealtime()
with (AlertDialog.Builder(ctx, R.style.Theme_AppCompat)) {
setMessage(String.format(ctx.getString(R.string.contact_action_question),
Contact.contacts()[position].name))
setNeutralButton(ctx.getText(R.string.cancel), dialogClickListener)
setNegativeButton(ctx.getText(R.string.call), dialogClickListener)
setPositiveButton(ctx.getText(R.string.send_message), dialogClickListener)
show()
if (SystemClock.elapsedRealtime() - lastClick > 1000) {
lastClick = SystemClock.elapsedRealtime()
with(AlertDialog.Builder(ctx, R.style.Theme_AppCompat)) {
setMessage(String.format(ctx.getString(R.string.contact_action_question),
contact.name))
setNeutralButton(ctx.getText(R.string.cancel), dialogClickListener)
setNegativeButton(ctx.getText(R.string.call), dialogClickListener)
setPositiveButton(ctx.getText(R.string.send_message), dialogClickListener)
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 {
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val id = contact.id
val avatarFile = File(BaresipService.filesPath, "$id.img")
if (avatarFile.exists()) {
try {
avatarFile.delete()
} catch (e: IOException) {
Log.e(TAG, "Could not delete file '$id.img")
when (contact) {
is Contact.BaresipContact -> {
val id = contact.id
val avatarFile = File(BaresipService.filesPath, "$id.img")
if (avatarFile.exists()) {
try {
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 -> {
}
@ -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
titleView.text = ctx.getString(R.string.confirmation)
with (AlertDialog.Builder(ctx)) {
with(AlertDialog.Builder(ctx)) {
setCustomTitle(titleView)
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)
setPositiveButton(ctx.getText(R.string.delete), dialogClickListener)
show()
@ -142,18 +200,6 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
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
}
}

View File

@ -4,10 +4,10 @@ import android.app.Activity
import android.content.*
import android.os.Bundle
import android.os.SystemClock
import android.view.Menu
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.Observer
import com.tutpro.baresip.databinding.ActivityContactsBinding
class ContactsActivity : AppCompatActivity() {
@ -27,10 +27,15 @@ class ContactsActivity : AppCompatActivity() {
Utils.addActivity("contacts,$aor")
val listView = binding.contacts
clAdapter = ContactListAdapter(this, Contact.contacts(), aor)
clAdapter = ContactListAdapter(this, BaresipService.contacts, aor)
listView.adapter = clAdapter
listView.isLongClickable = true
val contactObserver = Observer<Long> {
clAdapter.notifyDataSetChanged()
}
BaresipService.contactUpdate.observe(this, contactObserver)
val contactRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
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() {
super.onResume()
clAdapter.notifyDataSetChanged()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
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 -> {
BaresipService.activities.remove("contacts,$aor")
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.
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
Log.d(TAG, "aorSpinner selecting $position")
val acc = UserAgent.uas()[position].account
aorSpinner.tag = acc.aor
val ua = UserAgent.uas()[position]
showCall(ua)
updateIcons(acc)
if (position < UserAgent.uas().size) {
val acc = UserAgent.uas()[position].account
aorSpinner.tag = acc.aor
val ua = UserAgent.uas()[position]
showCall(ua)
updateIcons(acc)
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
Log.d(TAG, "Nothing selected")
@ -273,7 +275,7 @@ class MainActivity : AppCompatActivity() {
}
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames()))
Contact.contactNames()))
callUri.threshold = 2
callUri.setOnFocusChangeListener { view, b ->
if (b) {
@ -456,27 +458,18 @@ class MainActivity : AppCompatActivity() {
contactsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames()))
Contact.contactNames()))
}
contactsButton.setOnClickListener {
if (BaresipService.preferAndroidContacts) {
val i = Intent(this@MainActivity, AndroidContactsActivity::class.java)
if (aorSpinner.selectedItemPosition >= 0)
i.putExtra("aor", aorSpinner.tag.toString())
else
i.putExtra("aor", "")
startActivity(i)
} else {
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)
}
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()) {
@ -932,6 +925,13 @@ class MainActivity : AppCompatActivity() {
if (event == "started") {
val callActionUri = params[0]
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()
if (callActionUri != "") {
var ua = UserAgent.ofDomain(Utils.uriHostPart(callActionUri))
@ -1105,7 +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(Utils.contactName(ev[1]), Utils.aorDomain(aor))
val target = Utils.friendlyUri(Contact.contactName(ev[1]), Utils.aorDomain(aor))
with(AlertDialog.Builder(this)) {
setCustomTitle(titleView)
setMessage(String.format(getString(R.string.transfer_request_query),
@ -1403,7 +1403,7 @@ class MainActivity : AppCompatActivity() {
titleView.text = getString(R.string.call_transfer)
val transferUri = layout.findViewById(R.id.transferUri) as AutoCompleteTextView
transferUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames()))
Contact.contactNames()))
transferUri.threshold = 2
transferUri.requestFocus()
val builder = AlertDialog.Builder(this)
@ -1414,7 +1414,7 @@ class MainActivity : AppCompatActivity() {
dialog.dismiss()
var uriText = transferUri.text.toString().trim()
if (uriText.isNotEmpty()) {
uriText = Utils.contactUri(uriText) ?: uriText
uriText = Contact.contactUri(uriText) ?: uriText
if (Utils.isTelNumber(uriText))
uriText = "tel:$uriText"
val uri = if (Utils.isTelUri(uriText))
@ -1702,7 +1702,7 @@ class MainActivity : AppCompatActivity() {
if (Call.calls().isEmpty()) {
var uriText = callUri.text.toString().trim()
if (uriText.isNotEmpty()) {
uriText = Utils.contactUri(uriText) ?: uriText
uriText = Contact.contactUri(uriText) ?: uriText
if (Utils.isTelNumber(uriText))
uriText = "tel:$uriText"
val uri = if (Utils.isTelUri(uriText)) {
@ -1750,10 +1750,8 @@ class MainActivity : AppCompatActivity() {
} else {
val latest = NewCallHistory.aorLatestHistory(aor)
if (latest != null)
callUri.setText(
Utils.friendlyUri(Utils.contactName(latest.peerUri), Utils.aorDomain(aor)
)
)
callUri.setText(Utils.friendlyUri(Contact.contactName(latest.peerUri),
Utils.aorDomain(aor)))
}
}
}
@ -1772,7 +1770,7 @@ class MainActivity : AppCompatActivity() {
callUri.isFocusableInTouchMode = true
imm.hideSoftInputFromWindow(callUri.windowToken, 0)
callUri.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
Utils.contactNames()))
Contact.contactNames()))
securityButton.visibility = View.INVISIBLE
callButton.visibility = View.VISIBLE
callButton.isEnabled = true
@ -1797,7 +1795,7 @@ class MainActivity : AppCompatActivity() {
else
getString(R.string.outgoing_call_to_dots)
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)))
securityButton.visibility = View.INVISIBLE
callButton.visibility = View.INVISIBLE
@ -1812,7 +1810,7 @@ class MainActivity : AppCompatActivity() {
"incoming" -> {
callTitle.text = getString(R.string.incoming_call_from_dots)
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)))
callUri.setAdapter(null)
securityButton.visibility = View.INVISIBLE
@ -1832,7 +1830,7 @@ class MainActivity : AppCompatActivity() {
}
if (call.referTo != "") {
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)))
transferButton.isEnabled = false
} else {
@ -1840,7 +1838,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(Utils.contactName(call.peerUri),
callUri.setText(Utils.friendlyUri(Contact.contactName(call.peerUri),
Utils.aorDomain(ua.account.aor)))
transferButton.isEnabled = true
}
@ -1973,21 +1971,6 @@ class MainActivity : AppCompatActivity() {
i.putExtras(b)
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" -> {
val i = Intent(this, ChatsActivity::class.java)
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) {
lp.setMargins(0, 10, 75, 10)
viewHolder.layoutView.setBackgroundResource(R.drawable.message_in_bg)
val contactName = Utils.contactName(message.peerUri)
val contactName = Contact.contactName(message.peerUri)
if (contactName.startsWith("sip:") &&
(Utils.uriHostPart(message.peerUri) == Utils.uriHostPart(message.aor)))
Utils.uriUserPart(message.peerUri)

View File

@ -320,57 +320,43 @@ object Utils {
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) {
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)
when (val contact = Contact.findContact(uri)) {
is Contact.BaresipContact -> {
val avatarImage = contact.avatarImage
if (avatarImage != null) {
imageView.setImageBitmap(avatarImage)
} else {
textView.background.setTint(androidContact.color)
if (androidContact.name.isNotEmpty())
textView.text = "${androidContact.name[0]}"
textView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
textView.text = "${contact.name[0]}"
else
textView.text = ""
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)
imageView.setImageBitmap(bitmap)
}
}
}

View File

@ -215,37 +215,27 @@
android:id="@+id/AudioSettingsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="12dp"
android:textSize="18sp"
android:textStyle="bold"
android:text="@string/audio_settings" >
</TextView>
<RelativeLayout
<TextView
android:id="@+id/ContactsTitle"
android:layout_width="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:orientation="horizontal" >
<TextView
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>
android:paddingTop="8dp" >
</Spinner>
<RelativeLayout
android:layout_width="wrap_content"

View File

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

View File

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