rely on ContactsHelper from Simple Commons

This commit is contained in:
tibbi 2023-03-04 09:47:42 +01:00
parent c94caef4bb
commit 9c5978f50a
3 changed files with 1 additions and 454 deletions

View File

@ -63,7 +63,7 @@ android {
}
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:52fd420d35'
implementation 'com.github.SimpleMobileTools:Simple-Commons:fc000291b0'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61'
implementation 'com.github.tibbi:android-smsmms:33fcaf94d9'

View File

@ -68,10 +68,6 @@ class Config(context: Context) : BaseConfig(context) {
pinnedConversations = pinnedConversations.minus(conversations.map { it.threadId.toString() })
}
var lastExportPath: String
get() = prefs.getString(LAST_EXPORT_PATH, "")!!
set(lastExportPath) = prefs.edit().putString(LAST_EXPORT_PATH, lastExportPath).apply()
var exportSms: Boolean
get() = prefs.getBoolean(EXPORT_SMS, true)
set(exportSms) = prefs.edit().putBoolean(EXPORT_SMS, exportSms).apply()

View File

@ -1,449 +0,0 @@
package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.*
import android.provider.ContactsContract.Data
import android.text.TextUtils
import android.util.SparseArray
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.models.PhoneNumber
import com.simplemobiletools.commons.models.contacts.*
import com.simplemobiletools.commons.models.contacts.Email
import com.simplemobiletools.commons.models.contacts.Event
import com.simplemobiletools.commons.models.contacts.Organization
import com.simplemobiletools.commons.overloads.times
// based on the ContactsHelper from Simple-Contacts
class ContactsHelper(val context: Context) {
private var displayContactSources = ArrayList<String>()
fun getContactFromUri(uri: Uri): Contact? {
val key = getLookupKeyFromUri(uri) ?: return null
return getContactWithLookupKey(key)
}
private fun getLookupKeyFromUri(lookupUri: Uri): String? {
val projection = arrayOf(ContactsContract.Contacts.LOOKUP_KEY)
val cursor = context.contentResolver.query(lookupUri, projection, null, null, null)
cursor?.use {
if (cursor.moveToFirst()) {
return cursor.getStringValue(ContactsContract.Contacts.LOOKUP_KEY)
}
}
return null
}
private fun getContactWithLookupKey(key: String): Contact? {
val selection = "(${Data.MIMETYPE} = ? OR ${Data.MIMETYPE} = ?) AND ${Data.LOOKUP_KEY} = ?"
val selectionArgs = arrayOf(StructuredName.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, key)
return parseContactCursor(selection, selectionArgs)
}
private fun parseContactCursor(selection: String, selectionArgs: Array<String>): Contact? {
val storedGroups = getDeviceStoredGroups()
val uri = Data.CONTENT_URI
val projection = getContactProjection()
val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {
if (cursor.moveToFirst()) {
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
var prefix = ""
var firstName = ""
var middleName = ""
var surname = ""
var suffix = ""
val mimetype = cursor.getStringValue(Data.MIMETYPE)
// ignore names at Organization type contacts
if (mimetype == StructuredName.CONTENT_ITEM_TYPE) {
prefix = cursor.getStringValue(StructuredName.PREFIX) ?: ""
firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: ""
middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: ""
surname = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: ""
suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: ""
}
val nickname = getNicknames(id)[id] ?: ""
val photoUri = cursor.getStringValue(Phone.PHOTO_URI) ?: ""
val number = getPhoneNumbers(id)[id] ?: ArrayList()
val emails = getEmails(id)[id] ?: ArrayList()
val addresses = getAddresses(id)[id] ?: ArrayList()
val events = getEvents(id)[id] ?: ArrayList()
val notes = getNotes(id)[id] ?: ""
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
val starred = cursor.getIntValue(StructuredName.STARRED)
val ringtone = cursor.getStringValue(StructuredName.CUSTOM_RINGTONE)
val contactId = cursor.getIntValue(Data.CONTACT_ID)
val groups = getContactGroups(storedGroups, contactId)[contactId] ?: ArrayList()
val thumbnailUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val organization = getOrganizations(id)[id] ?: Organization("", "")
val websites = getWebsites(id)[id] ?: ArrayList()
val ims = getIMs(id)[id] ?: ArrayList()
return Contact(
id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims, mimetype, ringtone
)
}
}
return null
}
private fun getPhoneNumbers(contactId: Int? = null): SparseArray<ArrayList<PhoneNumber>> {
val phoneNumbers = SparseArray<ArrayList<PhoneNumber>>()
val uri = Phone.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Phone.NUMBER,
Phone.NORMALIZED_NUMBER,
Phone.TYPE,
Phone.LABEL,
Phone.IS_PRIMARY
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val number = cursor.getStringValue(Phone.NUMBER) ?: return@queryCursor
val normalizedNumber = cursor.getStringValue(Phone.NORMALIZED_NUMBER) ?: number.normalizePhoneNumber()
val type = cursor.getIntValue(Phone.TYPE)
val label = cursor.getStringValue(Phone.LABEL) ?: ""
val isPrimary = cursor.getIntValue(Phone.IS_PRIMARY) != 0
if (phoneNumbers[id] == null) {
phoneNumbers.put(id, ArrayList())
}
val phoneNumber = PhoneNumber(number, type, label, normalizedNumber, isPrimary)
phoneNumbers[id].add(phoneNumber)
}
return phoneNumbers
}
private fun getNicknames(contactId: Int? = null): SparseArray<String> {
val nicknames = SparseArray<String>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Nickname.NAME
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val nickname = cursor.getStringValue(Nickname.NAME) ?: return@queryCursor
nicknames.put(id, nickname)
}
return nicknames
}
private fun getEmails(contactId: Int? = null): SparseArray<ArrayList<Email>> {
val emails = SparseArray<ArrayList<Email>>()
val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.TYPE,
ContactsContract.CommonDataKinds.Email.LABEL
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA) ?: return@queryCursor
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Email.TYPE)
val label = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.LABEL) ?: ""
if (emails[id] == null) {
emails.put(id, ArrayList())
}
emails[id]!!.add(Email(email, type, label))
}
return emails
}
private fun getAddresses(contactId: Int? = null): SparseArray<ArrayList<Address>> {
val addresses = SparseArray<ArrayList<Address>>()
val uri = StructuredPostal.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
StructuredPostal.FORMATTED_ADDRESS,
StructuredPostal.TYPE,
StructuredPostal.LABEL
)
val selection = if (contactId == null) getSourcesSelection() else "${Data.RAW_CONTACT_ID} = ?"
val selectionArgs = if (contactId == null) getSourcesSelectionArgs() else arrayOf(contactId.toString())
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val address = cursor.getStringValue(StructuredPostal.FORMATTED_ADDRESS) ?: return@queryCursor
val type = cursor.getIntValue(StructuredPostal.TYPE)
val label = cursor.getStringValue(StructuredPostal.LABEL) ?: ""
if (addresses[id] == null) {
addresses.put(id, ArrayList())
}
addresses[id]!!.add(Address(address, type, label))
}
return addresses
}
private fun getIMs(contactId: Int? = null): SparseArray<ArrayList<IM>> {
val IMs = SparseArray<ArrayList<IM>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Im.DATA,
Im.PROTOCOL,
Im.CUSTOM_PROTOCOL
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Im.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val IM = cursor.getStringValue(Im.DATA) ?: return@queryCursor
val type = cursor.getIntValue(Im.PROTOCOL)
val label = cursor.getStringValue(Im.CUSTOM_PROTOCOL) ?: ""
if (IMs[id] == null) {
IMs.put(id, ArrayList())
}
IMs[id]!!.add(IM(IM, type, label))
}
return IMs
}
private fun getEvents(contactId: Int? = null): SparseArray<ArrayList<Event>> {
val events = SparseArray<ArrayList<Event>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Event.START_DATE,
ContactsContract.CommonDataKinds.Event.TYPE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE) ?: return@queryCursor
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.TYPE)
if (events[id] == null) {
events.put(id, ArrayList())
}
events[id]!!.add(Event(startDate, type))
}
return events
}
private fun getNotes(contactId: Int? = null): SparseArray<String> {
val notes = SparseArray<String>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Note.NOTE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Note.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val note = cursor.getStringValue(Note.NOTE) ?: return@queryCursor
notes.put(id, note)
}
return notes
}
private fun getOrganizations(contactId: Int? = null): SparseArray<Organization> {
val organizations = SparseArray<Organization>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Organization.COMPANY,
ContactsContract.CommonDataKinds.Organization.TITLE
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val company = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.COMPANY) ?: ""
val title = cursor.getStringValue(ContactsContract.CommonDataKinds.Organization.TITLE) ?: ""
if (company.isEmpty() && title.isEmpty()) {
return@queryCursor
}
val organization = Organization(company, title)
organizations.put(id, organization)
}
return organizations
}
private fun getWebsites(contactId: Int? = null): SparseArray<ArrayList<String>> {
val websites = SparseArray<ArrayList<String>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.RAW_CONTACT_ID,
Website.URL
)
val selection = getSourcesSelection(true, contactId != null)
val selectionArgs = getSourcesSelectionArgs(Website.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.RAW_CONTACT_ID)
val url = cursor.getStringValue(Website.URL) ?: return@queryCursor
if (websites[id] == null) {
websites.put(id, ArrayList())
}
websites[id]!!.add(url)
}
return websites
}
private fun getDeviceStoredGroups(): java.util.ArrayList<Group> {
val groups = java.util.ArrayList<Group>()
val uri = ContactsContract.Groups.CONTENT_URI
val projection = arrayOf(
ContactsContract.Groups._ID,
ContactsContract.Groups.TITLE,
ContactsContract.Groups.SYSTEM_ID
)
val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ?"
val selectionArgs = arrayOf("0", "0")
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getLongValue(ContactsContract.Groups._ID)
val title = cursor.getStringValue(ContactsContract.Groups.TITLE) ?: return@queryCursor
val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID)
if (groups.map { it.title }.contains(title) && systemId != null) {
return@queryCursor
}
groups.add(Group(id, title))
}
return groups
}
private fun getContactGroups(storedGroups: ArrayList<Group>, contactId: Int? = null): SparseArray<ArrayList<Group>> {
val groups = SparseArray<ArrayList<Group>>()
val uri = Data.CONTENT_URI
val projection = arrayOf(
Data.CONTACT_ID,
Data.DATA1
)
val selection = getSourcesSelection(true, contactId != null, false)
val selectionArgs = getSourcesSelectionArgs(GroupMembership.CONTENT_ITEM_TYPE, contactId)
context.queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
val id = cursor.getIntValue(Data.CONTACT_ID)
val newRowId = cursor.getLongValue(Data.DATA1)
val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: return@queryCursor
val group = Group(newRowId, groupTitle)
if (groups[id] == null) {
groups.put(id, ArrayList())
}
groups[id]!!.add(group)
}
return groups
}
private fun getQuestionMarks() = ("?," * displayContactSources.filter { it.isNotEmpty() }.size).trimEnd(',')
private fun getSourcesSelection(addMimeType: Boolean = false, addContactId: Boolean = false, useRawContactId: Boolean = true): String {
val strings = ArrayList<String>()
if (addMimeType) {
strings.add("${Data.MIMETYPE} = ?")
}
if (addContactId) {
strings.add("${if (useRawContactId) Data.RAW_CONTACT_ID else Data.CONTACT_ID} = ?")
} else {
// sometimes local device storage has null account_name, handle it properly
val accountNameString = StringBuilder()
if (displayContactSources.contains("")) {
accountNameString.append("(")
}
accountNameString.append("${ContactsContract.RawContacts.ACCOUNT_NAME} IN (${getQuestionMarks()})")
if (displayContactSources.contains("")) {
accountNameString.append(" OR ${ContactsContract.RawContacts.ACCOUNT_NAME} IS NULL)")
}
strings.add(accountNameString.toString())
}
return TextUtils.join(" AND ", strings)
}
private fun getSourcesSelectionArgs(mimetype: String? = null, contactId: Int? = null): Array<String> {
val args = ArrayList<String>()
if (mimetype != null) {
args.add(mimetype)
}
if (contactId != null) {
args.add(contactId.toString())
} else {
args.addAll(displayContactSources.filter { it.isNotEmpty() })
}
return args.toTypedArray()
}
private fun getContactProjection() = arrayOf(
Data.MIMETYPE,
Data.CONTACT_ID,
Data.RAW_CONTACT_ID,
StructuredName.PREFIX,
StructuredName.GIVEN_NAME,
StructuredName.MIDDLE_NAME,
StructuredName.FAMILY_NAME,
StructuredName.SUFFIX,
StructuredName.PHOTO_URI,
StructuredName.PHOTO_THUMBNAIL_URI,
StructuredName.STARRED,
StructuredName.CUSTOM_RINGTONE,
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsContract.RawContacts.ACCOUNT_TYPE
)
}