diff --git a/app/build.gradle b/app/build.gradle index b89f79e0..759e38dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt index 799e8ca5..b9b5c85b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Config.kt @@ -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() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt deleted file mode 100644 index 0bfc46db..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/ContactsHelper.kt +++ /dev/null @@ -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() - - 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): 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> { - val phoneNumbers = SparseArray>() - 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 { - val nicknames = SparseArray() - 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> { - val emails = SparseArray>() - 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> { - val addresses = SparseArray>() - 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> { - val IMs = SparseArray>() - 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> { - val events = SparseArray>() - 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 { - val notes = SparseArray() - 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 { - val organizations = SparseArray() - 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> { - val websites = SparseArray>() - 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 { - val groups = java.util.ArrayList() - - 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, contactId: Int? = null): SparseArray> { - val groups = SparseArray>() - - 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() - 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 { - val args = ArrayList() - - 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 - ) - -}