Added possibility to mark contact as favorite

This commit is contained in:
Juha Heinanen
2023-11-02 10:00:24 +02:00
parent 28da3c8fa1
commit ae46ee3b04
8 changed files with 96 additions and 35 deletions
@@ -69,7 +69,7 @@ class BaresipService: Service() {
private lateinit var wifiLock: WifiManager.WifiLock private lateinit var wifiLock: WifiManager.WifiLock
private lateinit var bluetoothReceiver: BroadcastReceiver private lateinit var bluetoothReceiver: BroadcastReceiver
private lateinit var hotSpotReceiver: BroadcastReceiver private lateinit var hotSpotReceiver: BroadcastReceiver
private lateinit var contentObserver: ContentObserver private lateinit var androidContactsObserver: ContentObserver
private lateinit var stopState: String private lateinit var stopState: String
private lateinit var quitTimer: CountDownTimer private lateinit var quitTimer: CountDownTimer
@@ -82,7 +82,7 @@ class BaresipService: Service() {
private var hotSpotIsEnabled = false private var hotSpotIsEnabled = false
private var hotSpotAddresses = mapOf<String, String>() private var hotSpotAddresses = mapOf<String, String>()
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var contentObserverRegistered = false private var androidContactsObserverRegistered = false
private var isServiceClean = false private var isServiceClean = false
@SuppressLint("WakelockTimeout") @SuppressLint("WakelockTimeout")
@@ -318,9 +318,9 @@ class BaresipService: Service() {
this.registerReceiver(bluetoothReceiver, filter) this.registerReceiver(bluetoothReceiver, filter)
} }
contentObserver = object : ContentObserver(null) { androidContactsObserver = object : ContentObserver(null) {
override fun onChange(self: Boolean) { override fun onChange(self: Boolean) {
Log.d(TAG, "Contacts change") Log.d(TAG, "Android contacts change")
if (contactsMode != "baresip") { if (contactsMode != "baresip") {
Contact.loadAndroidContacts(this@BaresipService.applicationContext) Contact.loadAndroidContacts(this@BaresipService.applicationContext)
Contact.contactsUpdate() Contact.contactsUpdate()
@@ -412,7 +412,7 @@ class BaresipService: Service() {
Contact.restoreBaresipContacts() Contact.restoreBaresipContacts()
if (contactsMode != "baresip") { if (contactsMode != "baresip") {
Contact.loadAndroidContacts(applicationContext) Contact.loadAndroidContacts(applicationContext)
registerContentObserver() registerAndroidContactsObserver()
} }
Contact.contactsUpdate() Contact.contactsUpdate()
@@ -470,11 +470,11 @@ class BaresipService: Service() {
} }
"Start Content Observer" -> { "Start Content Observer" -> {
registerContentObserver() registerAndroidContactsObserver()
} }
"Stop Content Observer" -> { "Stop Content Observer" -> {
unRegisterContentObserver() unRegisterAndroidContactsObserver()
} }
"Call Answer" -> { "Call Answer" -> {
@@ -1493,21 +1493,21 @@ class BaresipService: Service() {
} }
} }
private fun registerContentObserver() { private fun registerAndroidContactsObserver() {
if (!contentObserverRegistered) if (!androidContactsObserverRegistered)
try { try {
contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, contentObserver) true, androidContactsObserver)
contentObserverRegistered = true androidContactsObserverRegistered = true
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.i(TAG, "No Contacts permission") Log.i(TAG, "No Contacts permission")
} }
} }
private fun unRegisterContentObserver() { private fun unRegisterAndroidContactsObserver() {
if (contentObserverRegistered) { if (androidContactsObserverRegistered) {
contentResolver.unregisterContentObserver(contentObserver) contentResolver.unregisterContentObserver(androidContactsObserver)
contentObserverRegistered = false androidContactsObserverRegistered = false
} }
} }
@@ -1529,8 +1529,8 @@ class BaresipService: Service() {
proximityWakeLock.release() proximityWakeLock.release()
if (this::wifiLock.isInitialized) if (this::wifiLock.isInitialized)
wifiLock.release() wifiLock.release()
if (this::contentObserver.isInitialized) if (this::androidContactsObserver.isInitialized)
contentResolver.unregisterContentObserver(contentObserver) contentResolver.unregisterContentObserver(androidContactsObserver)
isServiceClean = true isServiceClean = true
} }
} }
@@ -12,7 +12,8 @@ import java.util.ArrayList
sealed class Contact { sealed class Contact {
class BaresipContact(var name: String, var uri: String, var color: Int, val id: Long): Contact() { class BaresipContact(var name: String, var uri: String, var color: Int, val id: Long,
var favorite: Boolean): Contact() {
var avatarImage: Bitmap? = null var avatarImage: Bitmap? = null
} }
@@ -110,7 +111,8 @@ sealed class Contact {
fun saveBaresipContacts() { fun saveBaresipContacts() {
var contents = "" var contents = ""
for (c in BaresipService.baresipContacts) for (c in BaresipService.baresipContacts)
contents += "\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color}\n" contents += "\"${c.name}\" <${c.uri}>;id=${c.id};color=${c.color}" +
";favorite=${if (c.favorite) "yes" else "no"}\n"
Utils.putFileContents(BaresipService.filesPath + "/contacts", Utils.putFileContents(BaresipService.filesPath + "/contacts",
contents.toByteArray()) contents.toByteArray())
} }
@@ -196,8 +198,9 @@ sealed class Contact {
idValue.toLong() idValue.toLong()
else else
baseId + contactNo baseId + contactNo
val favorite = Utils.paramValue(params, "favorite" ) == "yes"
Log.d(TAG, "Restoring contact $name, $uri, $color, $id") Log.d(TAG, "Restoring contact $name, $uri, $color, $id")
val contact = BaresipContact(name, uri, color, id) val contact = BaresipContact(name, uri, color, id, favorite)
val avatarFilePath = BaresipService.filesPath + "/$id.png" val avatarFilePath = BaresipService.filesPath + "/$id.png"
if (File(avatarFilePath).exists()) { if (File(avatarFilePath).exists()) {
try { try {
@@ -254,7 +257,7 @@ sealed class Contact {
private fun sortContacts() { private fun sortContacts() {
BaresipService.contacts.sortBy{ when (it) { BaresipService.contacts.sortBy{ when (it) {
is BaresipContact -> "1" + it.name is BaresipContact -> if (it.favorite) "0" + it.name else "1" + it.name
is AndroidContact -> if (it.favorite) "0" + it.name else "1" + it.name is AndroidContact -> if (it.favorite) "0" + it.name else "1" + it.name
}} }}
} }
@@ -36,6 +36,7 @@ class ContactActivity : AppCompatActivity() {
private lateinit var cardImageAvatarView: ImageView private lateinit var cardImageAvatarView: ImageView
private lateinit var nameView: EditText private lateinit var nameView: EditText
private lateinit var uriView: EditText private lateinit var uriView: EditText
private lateinit var favoriteCheck: CheckBox
private lateinit var androidCheck: CheckBox private lateinit var androidCheck: CheckBox
private lateinit var menu: Menu private lateinit var menu: Menu
@@ -65,6 +66,7 @@ class ContactActivity : AppCompatActivity() {
cardImageAvatarView = binding.ImageAvatar cardImageAvatarView = binding.ImageAvatar
nameView = binding.Name nameView = binding.Name
uriView = binding.Uri uriView = binding.Uri
favoriteCheck = binding.Favorite
androidCheck = binding.Android androidCheck = binding.Android
newContact = intent.getBooleanExtra("new", false) newContact = intent.getBooleanExtra("new", false)
@@ -87,6 +89,7 @@ class ContactActivity : AppCompatActivity() {
else else
uriView.setText(uri) uriView.setText(uri)
uOrI = uri uOrI = uri
favoriteCheck.isChecked = false
if ((BaresipService.contactsMode == "android")) { if ((BaresipService.contactsMode == "android")) {
androidCheck.isChecked = true androidCheck.isChecked = true
androidCheck.isClickable = false androidCheck.isClickable = false
@@ -109,6 +112,7 @@ class ContactActivity : AppCompatActivity() {
title = name title = name
nameView.setText(name) nameView.setText(name)
uriView.setText(contact.uri) uriView.setText(contact.uri)
favoriteCheck.isChecked = contact.favorite
uOrI = index.toString() uOrI = index.toString()
} }
@@ -250,13 +254,14 @@ class ContactActivity : AppCompatActivity() {
BaresipService.activities.removeAt(0) BaresipService.activities.removeAt(0)
return true return true
} else { } else {
contact = Contact.BaresipContact(newName, newUri, color, id) contact = Contact.BaresipContact(newName, newUri, color, id, favoriteCheck.isChecked)
} }
} else { } else {
contact = Contact.contacts()[index] as Contact.BaresipContact contact = Contact.contacts()[index] as Contact.BaresipContact
contact.uri = newUri contact.uri = newUri
contact.name = newName contact.name = newName
contact.color = color contact.color = color
contact.favorite = favoriteCheck.isChecked
} }
when (newAvatar) { when (newAvatar) {
@@ -286,6 +291,7 @@ class ContactActivity : AppCompatActivity() {
BaresipService.activities.remove("contact,$newContact,$uOrI") BaresipService.activities.remove("contact,$newContact,$uOrI")
val i = Intent(this, MainActivity::class.java) val i = Intent(this, MainActivity::class.java)
if (androidCheck.isChecked && contact.favorite)
i.putExtra("name", newName) i.putExtra("name", newName)
setResult(Activity.RESULT_OK, i) setResult(Activity.RESULT_OK, i)
finish() finish()
@@ -384,6 +390,7 @@ class ContactActivity : AppCompatActivity() {
.withValue(Data.MIMETYPE, mimeType) .withValue(Data.MIMETYPE, mimeType)
.withValue(Data.DATA1, contact.uri.substringAfter(":")) .withValue(Data.DATA1, contact.uri.substringAfter(":"))
.build()) .build())
if (contact.avatarImage != null) { if (contact.avatarImage != null) {
val photoData: ByteArray? = bitmapToPNGByteArray(contact.avatarImage!!) val photoData: ByteArray? = bitmapToPNGByteArray(contact.avatarImage!!)
if (photoData != null) { if (photoData != null) {
@@ -443,7 +450,7 @@ class ContactActivity : AppCompatActivity() {
return try { return try {
contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null) contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Adding of SIP URI $uri failed") Log.e(TAG, "Update of Android URI $uri failed")
0 0
} }
} }
@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.provider.ContactsContract import android.provider.ContactsContract
@@ -65,6 +66,10 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
viewHolder.nameView.text = contact.name viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f viewHolder.nameView.textSize = 20f
if (contact.favorite)
viewHolder.nameView.setTypeface(null, Typeface.ITALIC)
else
viewHolder.nameView.setTypeface(null, Typeface.NORMAL)
viewHolder.nameView.setPadding(6, 6, 0, 6) viewHolder.nameView.setPadding(6, 6, 0, 6)
if (aor != "") { if (aor != "") {
@@ -139,6 +144,10 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
viewHolder.nameView.text = contact.name viewHolder.nameView.text = contact.name
viewHolder.nameView.textSize = 20f viewHolder.nameView.textSize = 20f
if (contact.favorite)
viewHolder.nameView.setTypeface(null, Typeface.ITALIC)
else
viewHolder.nameView.setTypeface(null, Typeface.NORMAL)
viewHolder.nameView.setPadding(6, 6, 0, 6) viewHolder.nameView.setPadding(6, 6, 0, 6)
viewHolder.nameView.setOnClickListener { viewHolder.nameView.setOnClickListener {
@@ -153,15 +162,9 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
} }
} }
if (contact.favorite) {
viewHolder.actionView.setImageResource(R.drawable.star)
viewHolder.actionView.visibility = View.VISIBLE
} else {
viewHolder.actionView.visibility = View.GONE viewHolder.actionView.visibility = View.GONE
} }
}
viewHolder.nameView.setOnLongClickListener { viewHolder.nameView.setOnLongClickListener {
val dialogClickListener = DialogInterface.OnClickListener { _, which -> val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) { when (which) {
@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.* import android.content.*
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.provider.ContactsContract
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@@ -16,6 +17,7 @@ class ContactsActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactsBinding private lateinit var binding: ActivityContactsBinding
private lateinit var clAdapter: ContactListAdapter private lateinit var clAdapter: ContactListAdapter
private lateinit var aor: String private lateinit var aor: String
private var newAndroidName: String? = null
private var lastClick: Long = 0 private var lastClick: Long = 0
private val onBackPressedCallback = object : OnBackPressedCallback(true) { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
@@ -38,14 +40,30 @@ class ContactsActivity : AppCompatActivity() {
listView.adapter = clAdapter listView.adapter = clAdapter
listView.isLongClickable = true listView.isLongClickable = true
val contactObserver = Observer<Long> { val androidContactsObserver = Observer<Long> {
if (newAndroidName != null) {
val contentValues = ContentValues()
contentValues.put(ContactsContract.Contacts.STARRED, 1)
try {
this.contentResolver.update(
ContactsContract.RawContacts.CONTENT_URI, contentValues,
ContactsContract.Contacts.DISPLAY_NAME + "='" + newAndroidName + "'", null
)
} catch (e: Exception) {
Log.e(TAG, "Update of Android favorite failed")
}
newAndroidName = null
}
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
} }
BaresipService.contactUpdate.observe(this, contactObserver) BaresipService.contactUpdate.observe(this, androidContactsObserver)
val contactRequest = val contactRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) if (it.resultCode == RESULT_OK) {
if (it.data != null && it.data!!.hasExtra("name"))
newAndroidName = it.data!!.getStringExtra("name")
}
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
} }
@@ -88,11 +88,38 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="10dp" android:paddingTop="10dp"
android:orientation="horizontal" > android:orientation="horizontal" >
<TextView
android:id="@+id/FavoriteTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/Favorite"
android:textSize="18sp"
android:text="@string/favorite" >
</TextView>
<CheckBox
android:id="@+id/Favorite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_gravity="end"
android:checked="false" >
</CheckBox>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:orientation="horizontal" >
<TextView <TextView
android:id="@+id/AndroidTitle" android:id="@+id/AndroidTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/Android" android:layout_toStartOf="@id/Android"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/android" > android:text="@string/android" >
@@ -102,6 +129,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_gravity="end" android:layout_gravity="end"
android:checked="false" > android:checked="false" >
</CheckBox> </CheckBox>
+1
View File
@@ -429,6 +429,7 @@
<string name="contact_name">Nimi</string> <string name="contact_name">Nimi</string>
<string name="sip_or_tel_uri">SIP tai tel URI</string> <string name="sip_or_tel_uri">SIP tai tel URI</string>
<string name="user_domain_or_number">käyttäjä@domain tai puhelinnumero</string> <string name="user_domain_or_number">käyttäjä@domain tai puhelinnumero</string>
<string name="favorite">Suosikki</string>
<string name="invalid_contact">Virheellinen yhteystiedon nimi \'%1$s\'</string> <string name="invalid_contact">Virheellinen yhteystiedon nimi \'%1$s\'</string>
<string name="contact_already_exists">Yhteystieto \'%1$s\' on jo olemassa.</string> <string name="contact_already_exists">Yhteystieto \'%1$s\' on jo olemassa.</string>
<string name="invalid_contact_uri">Virheellinen SIP URI \'%1$s\'</string> <string name="invalid_contact_uri">Virheellinen SIP URI \'%1$s\'</string>
+1
View File
@@ -399,6 +399,7 @@
<string name="contact_name">Name</string> <string name="contact_name">Name</string>
<string name="sip_or_tel_uri">SIP or tel URI</string> <string name="sip_or_tel_uri">SIP or tel URI</string>
<string name="user_domain_or_number">user@domain or telephone number</string> <string name="user_domain_or_number">user@domain or telephone number</string>
<string name="favorite">Favorite</string>
<string name="invalid_contact">Invalid contact name \'%1$s\'</string> <string name="invalid_contact">Invalid contact name \'%1$s\'</string>
<string name="contact_already_exists">Contact \'%1$s\' already exists.</string> <string name="contact_already_exists">Contact \'%1$s\' already exists.</string>
<string name="invalid_contact_uri">Invalid SIP URI</string> <string name="invalid_contact_uri">Invalid SIP URI</string>