Combining various solutions here, and seeing there are duplicate records from the results (due to multiple accounts), I've decided to make a function that will prioritize common account-types over others. On this sample, I also ignore the records of completely empty/null names (if all are as such), but you can change this if you wish:
@RequiresPermission( allOf = [Manifest.permission.READ_CONTACTS])@WorkerThreadfun getContactIdToContactNameMap(context: Context): LongSparseArray<ContactObject> { val contactIdToContactObjectMap = LongSparseArray<ContactObject>() val contentResolver = context.contentResolver contentResolver.query(ContactsContract.Data.CONTENT_URI, arrayOf( ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE), ContactsContract.Data.MIMETYPE +" = ? AND " + ContactsContract.Data.IN_VISIBLE_GROUP +" = ?", arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, "1"), null)?.use { cursor -> // Log.d("AppLog", "got ${cursor.count} records for names") val colContactId = cursor.getColumnIndex( ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID) val colFirstName = cursor.getColumnIndex( ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME) val colFamilyName = cursor.getColumnIndex( ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME) val colMiddleName = cursor.getColumnIndex( ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME) val colAccountType = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE) val googleAccount = "com.google" //https://stackoverflow.com/a/44802016/878126 val prioritizedAccountTypes = hashSetOf("vnd.sec.contact.phone", "com.htc.android.pcsc","com.sonyericsson.localcontacts", "com.lge.sync", "com.lge.phone","vnd.tmobileus.contact.phone", "com.android.huawei.phone","Local Phone Account","") val contactIdToAccountTypeMap = LongSparseArray<String>() while (cursor.moveToNext()) { val contactId = cursor.getLong(colContactId) val accountType = cursor.getString(colAccountType).orEmpty() val existingContact = contactIdToContactObjectMap.get(contactId) if (existingContact != null) { //this can occur, as we go over all of the items, including duplicate ones made by various sources // https://stackoverflow.com/a/4599474/878126 val previousAccountType = contactIdToAccountTypeMap.get(contactId) //google account is most prioritized, so we skip current one if previous was of it if (previousAccountType == googleAccount) continue if (accountType != googleAccount && previousAccountType != null && prioritizedAccountTypes.contains( previousAccountType)) //we got now a name of an account that isn't prioritized, but we already had a prioritized one, so ignore continue } contactIdToAccountTypeMap.put(contactId, accountType) val firstName = cursor.getString(colFirstName)?.trim() val lastName = cursor.getString(colFamilyName)?.trim() val middleName = cursor.getString(colMiddleName)?.trim() if (firstName.isNullOrBlank() && lastName.isNullOrBlank() && middleName.isNullOrBlank()) continue val contactObject = existingContact ?: ContactObject() contactObject.firstName = firstName contactObject.lastName = lastName contactObject.middleName = middleName contactIdToContactObjectMap.put(contactId, contactObject) } } return contactIdToContactObjectMap}class ContactObject { var firstName: String? = null var middleName: String? = null var lastName: String? = null}
Usage:
thread { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { val contactIdToContactNameMap = getContactIdToContactNameMap(this) Log.d("AppLog", "found ${contactIdToContactNameMap.size()} names for contacts") } else Log.d("AppLog", "no contacts permission...")}