Files

383 lines
16 KiB
Kotlin

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 com.tutpro.baresip.BaresipService.Companion.contactNames
import java.io.File
import java.util.ArrayList
sealed class Contact {
data class ContactUri(var uri: String, var label: String = "")
abstract fun name(): String
abstract fun id(): Long
abstract fun favorite(): Boolean
abstract fun colorInt(): Int
abstract fun uris(): List<ContactUri>
abstract fun email(): String
class BaresipContact(var name: String, val uris: ArrayList<ContactUri>, var email: String, var color: Int,
var id: Long, var favorite: Boolean): Contact() {
var avatarImage: Bitmap? = null
override fun name() = name
override fun id() = id
override fun favorite() = favorite
override fun colorInt() = color
override fun uris() = uris
override fun email() = email
}
class AndroidContact(var name: String, val uris: ArrayList<ContactUri>, var email: String, var color: Int,
var thumbnailUri: Uri?, var id: Long, var favorite: Boolean): Contact() {
override fun name() = name
override fun id() = id
override fun favorite() = favorite
override fun colorInt() = color
override fun uris() = uris
override fun email() = email
}
fun color(): String {
// Mask with 0xFFFFFF to ensure we get the standard 6-character hex code (RRGGBB)
// ignoring the alpha channel for compatibility with simple string parsers.
return String.format("#%06X", 0xFFFFFF and colorInt())
}
fun copy(): Contact {
val copy = when (this) {
is BaresipContact ->
BaresipContact(name, ArrayList(uris), email, color, id, favorite)
is AndroidContact ->
AndroidContact(name, ArrayList(uris), email, color, thumbnailUri, id, favorite)
}
if (this is BaresipContact)
(copy as BaresipContact).avatarImage = this.avatarImage
return copy
}
companion object {
// Return contact name of uri or uri itself if contact with uri is not found
fun contactName(uri: String): String {
var contact = findContact(uri)
if (contact == null) {
val userPart = Utils.uriUserPart(uri).replace("%23", "#")
if (Utils.isTelNumber(userPart)) {
contact = findContact("tel:$userPart")
}
}
if (contact != null)
return contact.name()
return uri
}
fun baresipContact(name: String): BaresipContact? {
synchronized(BaresipService.baresipContacts) {
for (c in BaresipService.baresipContacts.value)
if (c.name == name)
return c
}
return null
}
fun androidContact(name: String): AndroidContact? {
synchronized(BaresipService.androidContacts) {
for (c in BaresipService.androidContacts.value)
if (c.name == name)
return c
}
return null
}
fun contactContactUris(name: String, tel: Boolean = false): List<ContactUri> {
synchronized(BaresipService.contacts) {
for (c in BaresipService.contacts)
when (c) {
is BaresipContact -> {
if (c.name.equals(name, ignoreCase = true)) {
return if (tel)
c.uris.filter { it.uri.startsWith("tel:") }
else
c.uris
}
}
is AndroidContact -> {
if (c.name == name) {
return if (tel)
c.uris.filter { it.uri.startsWith("tel:") }
else
c.uris
}
}
}
}
return emptyList()
}
fun findContact(uri: String): Contact? {
synchronized(BaresipService.contacts) {
for (c in BaresipService.contacts)
when (c) {
is BaresipContact -> {
for (u in c.uris)
if (Utils.uriMatch(u.uri, uri))
return c
}
is AndroidContact -> {
val cleanUri = uri.filterNot { setOf('-', ' ', '(', ')').contains(it) }
for (u in c.uris)
if (Utils.uriMatch(
u.uri.filterNot { setOf('-', ' ', '(', ')').contains(it) },
cleanUri
)
)
return c
}
}
}
return null
}
fun nameExists(name: String, list: List<Contact>, ignoreCase: Boolean): Boolean {
for (c in list)
if (c.name().equals(name, ignoreCase = ignoreCase))
return true
return false
}
fun saveBaresipContacts() {
val avatarFiles = avatarFileNames()
var contents = ""
val contacts = synchronized(BaresipService.baresipContacts) {
BaresipService.baresipContacts.value.toList()
}
for (c in contacts) {
val uris = c.uris.joinToString(",") {
if (it.label.isNotEmpty()) "${it.uri}[${it.label}]" else it.uri
}
contents += "\"${c.name}\" <$uris>;email=${c.email};id=${c.id}" +
";color=${c.color};favorite=${if (c.favorite) "yes" else "no"}\n"
avatarFiles.remove(c.id.toString() + ".png")
}
Utils.putFileContents(BaresipService.filesPath + "/contacts",
contents.toByteArray())
for (f in avatarFiles)
File(BaresipService.filesPath + "/" + f).delete()
}
fun loadAndroidContacts(ctx: Context) {
// If phone type is needed, add DATA2 to projection. Then phone type can be got from
// cursor using getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.DATA2, ContactsContract.Data.PHOTO_THUMBNAIL_URI,
ContactsContract.Contacts.STARRED)
val selection =
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "' OR " +
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null)
BaresipService.androidContacts.value = listOf()
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) ?: continue
val type = cur.getInt(4)
val thumb = cur.getString(5)?.toUri()
val starred = cur.getInt(6)
val contact = if (contacts.containsKey(id))
contacts[id]!!
else
AndroidContact(name, ArrayList<ContactUri>(), "", Utils.randomColor(), thumb, id, starred == 1)
if (contact.name == "" && name != "")
contact.name = name
if (contact.thumbnailUri == null && thumb != null)
contact.thumbnailUri = thumb
when (mime) {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
val uri = "tel:${data.filterNot { setOf('-', ' ', '(', ')').contains(it) }}"
val label = typeToString(type)
if (contact.uris.none { it.uri == uri })
contact.uris.add(ContactUri(uri, label))
}
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
val uri = "sip:$data"
if (contact.uris.none { it.uri == uri })
contact.uris.add(ContactUri(uri, ""))
}
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
if (contact.email == "")
contact.email = data
}
}
if (!contacts.containsKey(id))
contacts[id] = contact
}
cur?.close()
val newList = mutableListOf<AndroidContact>()
for ((_, value) in contacts)
if (value.name != "" && (value.uris.isNotEmpty() || value.email != ""))
newList.add(value)
BaresipService.androidContacts.value = newList.toList()
}
private fun typeToString(type: Int): String {
return when(type) {
ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> "Home"
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> "Mobile"
ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> "Work"
else -> ""
}
}
fun stringToType(label: String): Int {
return when (label) {
"Home" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
"Mobile" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
"Work" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
"" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER
else -> ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM
}
}
fun restoreBaresipContacts(): Boolean {
val content = Utils.getFileContents(BaresipService.filesPath + "/contacts")
?: return false
val contacts = String(content)
var contactNo = 0
val baseId = System.currentTimeMillis()
val newBaresipContacts = mutableListOf<BaresipContact>()
contacts.lines().forEach {
val parts = it.split("\"")
if (parts.size == 3) {
contactNo++
val name = parts[1]
val uriParams = parts[2].trim()
val urisPart = uriParams.substringAfter("<").substringBefore(">")
val uris = ArrayList<ContactUri>()
for (uPart in urisPart.split(",")) {
if (uPart.isEmpty()) continue
if (uPart.contains("[") && uPart.contains("]")) {
val uri = uPart.substringBefore("[")
val label = uPart.substringAfter("[").substringBefore("]")
uris.add(ContactUri(uri, label))
} else {
uris.add(ContactUri(uPart, ""))
}
}
val params = uriParams.substringAfter(">;")
val email = Utils.paramValue(params, "email")
val colorValue = Utils.paramValue(params, "color" )
val color: Int = if (colorValue != "")
colorValue.toInt()
else
Utils.randomColor()
val idValue = Utils.paramValue(params, "id" )
val id: Long = if (idValue != "")
idValue.toLong()
else
baseId + contactNo
val favorite = Utils.paramValue(params, "favorite" ) == "yes"
// Log.d(TAG, "Restoring contact $name, $urisPart, $color, $id")
val contact = BaresipContact(name, uris, email, color, id, favorite)
val avatarFilePath = BaresipService.filesPath + "/$id.png"
if (File(avatarFilePath).exists()) {
try {
contact.avatarImage = BitmapFactory.decodeFile(avatarFilePath)
Log.d(TAG, "Set avatarImage")
if (contact.avatarImage == null)
Log.d(TAG, "Contact $id avatarImage is null")
} catch (e: Exception) {
Log.e(TAG, "Could not read avatar image from file $id.png: ${e.message}")
}
}
newBaresipContacts.add(contact)
}
}
BaresipService.baresipContacts.value = newBaresipContacts.toList()
return true
}
fun contactsUpdate() {
val newContacts = mutableListOf<Contact>()
if (BaresipService.contactsMode != "android") {
val baresipContacts = synchronized(BaresipService.baresipContacts) {
BaresipService.baresipContacts.value.toList()
}
for (c in baresipContacts)
newContacts.add(c.copy())
}
if (BaresipService.contactsMode != "baresip") {
val androidContacts = synchronized(BaresipService.androidContacts) {
BaresipService.androidContacts.value.toList()
}
for (c in androidContacts)
if (!nameExists(c.name, newContacts, true))
newContacts.add(c.copy())
}
newContacts.sortBy {
when (it) {
is BaresipContact -> if (it.favorite) "0" + it.name else "1" + it.name
is AndroidContact -> if (it.favorite) "0" + it.name else "1" + it.name
}
}
BaresipService.contacts = newContacts
generateContactNames()
}
fun addBaresipContact(contact: BaresipContact) {
synchronized(BaresipService.baresipContacts) {
BaresipService.baresipContacts.value += contact
}
saveBaresipContacts()
contactsUpdate()
}
fun updateBaresipContact(id: Long, contact: BaresipContact) {
synchronized(BaresipService.baresipContacts) {
val updatedContacts = BaresipService.baresipContacts.value.toMutableList()
updatedContacts.removeIf { it.id == id }
updatedContacts.add(contact)
BaresipService.baresipContacts.value = updatedContacts.toList()
}
saveBaresipContacts()
contactsUpdate()
}
fun removeBaresipContact(contact: BaresipContact) {
synchronized(BaresipService.baresipContacts) {
val removed = BaresipService.baresipContacts.value.toMutableList()
removed.removeIf { it.id == contact.id }
BaresipService.baresipContacts.value = removed.toList()
}
saveBaresipContacts()
contactsUpdate()
}
private fun generateContactNames () {
val newList = mutableListOf<String>()
for (c in BaresipService.contacts)
when (c) {
is BaresipContact ->
newList.add(c.name)
is AndroidContact ->
newList.add(c.name)
}
contactNames.value = newList.toList()
}
private fun avatarFileNames(): MutableList<String> {
return File(BaresipService.filesPath).list()!!.filter{ it.endsWith(".png")}.toMutableList()
}
}
}