package sh.lajo.buddy import android.Manifest import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.Looper import android.provider.ContactsContract import android.util.Log import androidx.core.content.ContextCompat class ContactsObserver( private val context: Context, private val contentResolver: ContentResolver ) : ContentObserver(Handler(Looper.getMainLooper())) { companion object { private const val TAG = "ContactsObserver" } private var lastContactCount = -1 init { // Initialize the contact count lastContactCount = getContactCount() } override fun onChange(selfChange: Boolean) { onChange(selfChange, null) } override fun onChange(selfChange: Boolean, uri: Uri?) { // Check if we have READ_CONTACTS permission if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_CONTACTS ) != PackageManager.PERMISSION_GRANTED ) { Log.w(TAG, "No READ_CONTACTS permission, skipping contact change") return } Log.d(TAG, "Contact change detected, URI: $uri") // Get current contact count val currentCount = getContactCount() // If count increased, a contact was likely added if (lastContactCount >= 0 && currentCount > lastContactCount) { Log.d(TAG, "Contact added detected (count: $lastContactCount -> $currentCount)") // Find and send the new contact(s) findAndSendNewContacts() } lastContactCount = currentCount } private fun getContactCount(): Int { try { val cursor = contentResolver.query( ContactsContract.Contacts.CONTENT_URI, arrayOf(ContactsContract.Contacts._ID), null, null, null ) cursor?.use { return it.count } } catch (e: Exception) { Log.e(TAG, "Error getting contact count", e) } return 0 } private fun findAndSendNewContacts() { try { // Query the most recently added contacts val cursor = contentResolver.query( ContactsContract.Contacts.CONTENT_URI, arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME ), null, null, "${ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP} DESC" ) cursor?.use { if (it.moveToFirst()) { // Get the most recent contact val contactId = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts._ID)) val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)) val phoneNumbers = getPhoneNumbers(contactId) val emails = getEmails(contactId) // Send the contact info via WebSocket sendContactAddedEvent(name, phoneNumbers, emails) } } } catch (e: Exception) { Log.e(TAG, "Error finding new contacts", e) } } private fun getPhoneNumbers(contactId: String): List { val phoneNumbers = mutableListOf() try { val cursor = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER), "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?", arrayOf(contactId), null ) cursor?.use { while (it.moveToNext()) { val number = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)) phoneNumbers.add(number) } } } catch (e: Exception) { Log.e(TAG, "Error getting phone numbers", e) } return phoneNumbers } private fun getEmails(contactId: String): List { val emails = mutableListOf() try { val cursor = contentResolver.query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, arrayOf(ContactsContract.CommonDataKinds.Email.ADDRESS), "${ContactsContract.CommonDataKinds.Email.CONTACT_ID} = ?", arrayOf(contactId), null ) cursor?.use { while (it.moveToNext()) { val email = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS)) emails.add(email) } } } catch (e: Exception) { Log.e(TAG, "Error getting emails", e) } return emails } private fun sendContactAddedEvent(name: String, phoneNumbers: List, emails: List) { Log.d(TAG, "Sending contact added event: $name, phones: $phoneNumbers, emails: $emails") val intent = Intent(context, WebSocketService::class.java).apply { action = WebSocketService.ACTION_SEND_CONTACT putExtra(WebSocketService.EXTRA_CONTACT_NAME, name) putExtra(WebSocketService.EXTRA_CONTACT_PHONES, phoneNumbers.toTypedArray()) putExtra(WebSocketService.EXTRA_CONTACT_EMAILS, emails.toTypedArray()) } context.startForegroundService(intent) } fun register() { contentResolver.registerContentObserver( ContactsContract.Contacts.CONTENT_URI, true, this ) Log.d(TAG, "ContactsObserver registered") } fun unregister() { contentResolver.unregisterContentObserver(this) Log.d(TAG, "ContactsObserver unregistered") } }