如何从 ContactsProvider 检索上次编辑的联系人

问题描述 投票:0回答:2

我必须显示添加的新联系人或编辑的联系人。我能够获取新添加的联系人,但无法获取上次编辑的联系人。我尝试根据 CONTACT_LAST_UPDATED_TIMESTAMP 检索已编辑的联系人,但如果我们正在拨打任何电话,则被叫联系人的 CONTACT_LAST_UPDATED_TIMESTAMP 会在 ContactsProvider 中进行修改,因此它会返回我上次呼叫的联系人作为上次编辑的联系人。我已经写了如下查询:

Cursor cursor = context.getContentResolver().query(uri, null,
                null,
                null,
                ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " DESC LIMIT 1");
android
2个回答
0
投票

您应该使用 ContactsContract.Contacts._ID 来代替 ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP

Cursor cursor = context.getContentResolver().query(uri, null,
                null,
                null,
                ContactsContract.Contacts._ID + " DESC LIMIT 1");

0
投票

您可以使用

registerContentObserver(ContactsContract.Contacts.CONTENT_URI...)
监控特定联系人的变化。

然后检查 CONTACT_LAST_UPDATED_TIMESTAMP 的值,并与之前的值进行比较。

当不同时,就该比较新字段和旧字段(如果没有旧字段则初始化。

我准备了一个可以帮助监控的ViewModel。你可以观察它的

contactStateLiveData
,当它发生变化时,就该查询联系人的字段了。您可以在
contactStateLiveData
内的viewModel本身中获取更多为您准备的字段,或者您可以在每次更改时以不同的方式进行查询。

我还让它每 500 毫秒更改一次,因为它有时会被多次调用。

class ABContactDetailsFragmentViewModel(application: Application) : BaseViewModel(application) {
    private val updateContactDebounceJob = DebounceJob()
    private var contactKeyToObserver: Pair<Uri, ContentObserver>? = null
    val contactStateLiveData = MutableLiveData<ContactState>(ContactState.Unknown)

    sealed class ContactState {
        data object Unknown : ContactState()
        class Exists(val lastUpdated: Long) : ContactState()
        data object Removed : ContactState()
    }

    /**monitor for changes of the specific contactKey of the contact.
     * Note that you should also call [checkUpdatesOfContactIfNeeded] after that, preferably on onResume as it might get the callback a bit late*/
    @RequiresPermission(permission.READ_CONTACTS)
    @UiThread
    fun monitorContact(contactKey: String) {
        val contentResolver = applicationContext.contentResolver
        val lookupUri: Uri = ContactsContract.Contacts.getLookupUri(contentResolver, Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, contactKey))
        monitorContact(lookupUri)
    }

    /**monitor for changes of the specific uri of the contact.
     * Note that you should also call [checkUpdatesOfContactIfNeeded] after that, preferably on onResume as it might get the callback a bit late*/
    @RequiresPermission(permission.READ_CONTACTS)
    @UiThread
    fun monitorContact(lookupUri: Uri) {
        val contentResolver = applicationContext.contentResolver
        contactKeyToObserver?.let { contactKeyToObserver ->
            if (contactKeyToObserver.first == lookupUri) return
            applicationContext.contentResolver.unregisterContentObserver(contactKeyToObserver.second)
            [email protected] = null
            contactStateLiveData.value = ContactState.Unknown
        }

        val observer = object : ContentObserver(Executors.uiHandler) {
            override fun onChange(selfChange: Boolean) {
                super.onChange(selfChange)
                checkUpdatesOfContactIfNeeded()
            }
        }
        contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, false, observer)
        onClearedListeners.add {
            contentResolver.unregisterContentObserver(observer)
        }
        this.contactKeyToObserver = Pair(lookupUri, observer)
    }

    @UiThread
    fun checkUpdatesOfContactIfNeeded() {
//        Log.d("AppLog", "ABContactDetailsFragmentViewModel checkUpdatesOfContactIfNeeded")
        val lookupUri = contactKeyToObserver?.first ?: return
        updateContactDebounceJob.debounce(scope = viewModelScope, runnable = {
            viewModelScope.launch {
                runInterruptible(Dispatchers.IO) {
//                    Log.d("AppLog", "ABContactDetailsFragmentViewModel scan for changes of monitored contact in background thread")
                    checkIfContactUpdated(lookupUri)
                }
            }
        })
    }

    @WorkerThread
    private fun checkIfContactUpdated(lookupUri: Uri) {
//        Log.d("AppLog", "ABContactDetailsFragmentViewModel checkIfContactUpdated lookupUri:$lookupUri")
        val state = runOnUiThreadWithResult {
            contactStateLiveData.value
        }
        val lastUpdatedTimeStampMs: Long? = if (state is ContactState.Exists) {
            state.lastUpdated
        } else null

        val projection: Array<out String> = arrayOf(
//                        ContactsContract.Contacts._ID,
//                ContactsContract.Contacts.LOOKUP_KEY,
//                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
                ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
        )
        val cursor = applicationContext.contentResolver.query(lookupUri, projection, null, null, null)
        if (cursor == null || cursor.count == 0) {
//            Log.d("AppLog", "ABContactDetailsFragmentViewModel contact not found")
            cursor?.closeQuietly()
            contactStateLiveData.postValue(ContactState.Removed)
            return
        }
        cursor.use {
            cursor.moveToNext()
            val timestampIdx = cursor.getColumnIndex(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)
            val timestampMs = cursor.getLong(timestampIdx)
            val detectedChange = lastUpdatedTimeStampMs != timestampMs
//            Log.d("AppLog", "ABContactDetailsFragmentViewModel detected change ? $detectedChange $lastUpdatedTimeStampMs->$timestampMs ${DatabaseUtils.dumpCurrentRowToString(cursor)} ")
            if (detectedChange) {
                contactStateLiveData.postValue(ContactState.Exists(timestampMs))
            }
        }
    }
}
//https://stackoverflow.com/questions/50858684/kotlin-android-debounce
class DebounceJob(private val defaultDebounceDelayMs:Long=500L) {
    private var job: Job? = null

    @UiThread
    fun debounce(delayMs: Long = defaultDebounceDelayMs, scope: CoroutineScope, runnable: Runnable) {
        job?.cancel()
        job = scope.launch {
            delay(delayMs)
            runnable.run()
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.