Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit acb1d7fc authored by Wolf-Martell Montwé's avatar Wolf-Martell Montwé Committed by GitHub
Browse files

Merge pull request #6730 from thundernest/add_contact_data_source

Add ContactDataSource
parents 401c954b 3ed19f00
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ val mainModule = module {
    single { get<Context>().resources }
    single { get<Context>().contentResolver }
    single { LocalStoreProvider() }
    single { Contacts(context = get()) }
    single { Contacts(contactDataSource = get()) }
    single { LocalKeyStore(directoryProvider = get()) }
    single { TrustManagerFactory.createInstance(get()) }
    single { LocalKeyStoreManager(get()) }
+14 −116
Original line number Diff line number Diff line
package com.fsck.k9.helper

import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
import androidx.core.content.ContextCompat
import app.k9mail.core.android.common.database.EmptyCursor
import app.k9mail.core.android.common.contact.ContactDataSource
import app.k9mail.core.common.mail.EmailAddress
import com.fsck.k9.mail.Address
import timber.log.Timber
@@ -18,9 +10,8 @@ import timber.log.Timber
 * Helper class to access the contacts stored on the device.
 */
open class Contacts(
    private var context: Context,
    private val contactDataSource: ContactDataSource,
) {
    private var contentResolver: ContentResolver = context.contentResolver

    /**
     * Check whether the provided email address belongs to one of the contacts.
@@ -29,17 +20,7 @@ open class Contacts(
     * @return <tt>true</tt>, if the email address belongs to a contact.
     * <tt>false</tt>, otherwise.
     */
    fun isInContacts(emailAddress: EmailAddress): Boolean {
        var result = false
        val cursor = getContactFor(emailAddress)
        if (cursor != null) {
            if (cursor.count > 0) {
                result = true
            }
            cursor.close()
        }
        return result
    }
    fun isInContacts(emailAddress: EmailAddress): Boolean = contactDataSource.hasContactFor(emailAddress)

    /**
     * Check whether one of the provided email addresses belongs to one of the contacts.
@@ -52,15 +33,8 @@ open class Contacts(
        emailAddresses.any { emailAddress -> isInContacts(emailAddress) }

    fun getContactUri(emailAddress: EmailAddress): Uri? {
        val cursor = getContactFor(emailAddress) ?: return null
        cursor.use {
            if (!cursor.moveToFirst()) {
                return null
            }
            val contactId = cursor.getLong(CONTACT_ID_INDEX)
            val lookupKey = cursor.getString(LOOKUP_KEY_INDEX)
            return ContactsContract.Contacts.getLookupUri(contactId, lookupKey)
        }
        val contact = contactDataSource.getContactFor(emailAddress)
        return contact?.uri
    }

    /**
@@ -74,17 +48,14 @@ open class Contacts(
        if (nameCache.containsKey(emailAddress)) {
            return nameCache[emailAddress]
        }
        val cursor = getContactFor(emailAddress)
        var name: String? = null
        if (cursor != null) {
            if (cursor.count > 0) {
                cursor.moveToFirst()
                name = cursor.getString(NAME_INDEX)
            }
            cursor.close()

        val contact = contactDataSource.getContactFor(emailAddress)
        return if (contact != null) {
            nameCache[emailAddress] = contact.name
            contact.name
        } else {
            null
        }
        nameCache[emailAddress] = name
        return name
    }

    /**
@@ -105,88 +76,15 @@ open class Contacts(
     */
    fun getPhotoUri(emailAddress: EmailAddress): Uri? {
        return try {
            val cursor = getContactFor(emailAddress) ?: return null
            try {
                if (!cursor.moveToFirst()) {
                    return null
                }
                val columnIndex = cursor.getColumnIndex(CommonDataKinds.Photo.PHOTO_URI)
                val uriString = cursor.getString(columnIndex) ?: return null
                Uri.parse(uriString)
            } catch (e: IllegalStateException) {
                null
            } finally {
                cursor.close()
            }
            val contact = contactDataSource.getContactFor(emailAddress)
            contact?.photoUri
        } catch (e: Exception) {
            Timber.e(e, "Couldn't fetch photo for contact with email ${emailAddress.address}")
            null
        }
    }

    private fun hasContactPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.READ_CONTACTS,
        ) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Return a [Cursor] instance that can be used to fetch information
     * about the contact with the given email address.
     *
     * @param emailAddress The email address to search for.
     * @return A [Cursor] instance that can be used to fetch information
     * about the contact with the given email address
     */
    private fun getContactFor(emailAddress: EmailAddress): Cursor? {
        val uri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress.address))
        return if (hasContactPermission()) {
            contentResolver.query(
                uri,
                PROJECTION,
                null,
                null,
                SORT_ORDER,
            )
        } else {
            EmptyCursor()
        }
    }

    companion object {
        /**
         * The order in which the search results are returned by
         * [.getContactBy].
         */
        private const val SORT_ORDER = CommonDataKinds.Email.TIMES_CONTACTED + " DESC, " +
            ContactsContract.Contacts.DISPLAY_NAME + ", " +
            CommonDataKinds.Email._ID

        /**
         * Array of columns to load from the database.
         */
        private val PROJECTION = arrayOf(
            CommonDataKinds.Email._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            CommonDataKinds.Email.CONTACT_ID,
            CommonDataKinds.Photo.PHOTO_URI,
            ContactsContract.Contacts.LOOKUP_KEY,
        )

        /**
         * Index of the name field in the projection. This must match the order in
         * [.PROJECTION].
         */
        private const val NAME_INDEX = 1

        /**
         * Index of the contact id field in the projection. This must match the order in
         * [.PROJECTION].
         */
        private const val CONTACT_ID_INDEX = 2
        private const val LOOKUP_KEY_INDEX = 4

        private val nameCache = HashMap<EmailAddress, String?>()

        /**
+4 −4
Original line number Diff line number Diff line
package com.fsck.k9.mailstore

import android.content.ContentValues
import com.fsck.k9.helper.getIntOrThrow
import com.fsck.k9.helper.getLongOrThrow
import com.fsck.k9.helper.getStringOrNull
import com.fsck.k9.helper.getStringOrThrow
import app.k9mail.core.android.common.database.getIntOrThrow
import app.k9mail.core.android.common.database.getLongOrThrow
import app.k9mail.core.android.common.database.getStringOrNull
import app.k9mail.core.android.common.database.getStringOrThrow
import kotlinx.datetime.Clock

class OutboxStateRepository(
+4 −6
Original line number Diff line number Diff line
package com.fsck.k9.helper

import android.content.Context
import android.graphics.Color
import android.text.SpannableString
import app.k9mail.core.common.mail.EmailAddress
@@ -12,7 +11,7 @@ import com.fsck.k9.helper.MessageHelper.Companion.toFriendly
import com.fsck.k9.mail.Address
import org.junit.Before
import org.junit.Test
import org.robolectric.RuntimeEnvironment
import org.mockito.kotlin.mock

class MessageHelperTest : RobolectricTest() {

@@ -22,9 +21,8 @@ class MessageHelperTest : RobolectricTest() {

    @Before
    fun setUp() {
        val context: Context = RuntimeEnvironment.getApplication()
        contacts = Contacts(context)
        contactsWithFakeContact = object : Contacts(context) {
        contacts = mock()
        contactsWithFakeContact = object : Contacts(mock()) {
            override fun getNameFor(emailAddress: EmailAddress): String? {
                return if ("test@testor.com" == emailAddress.address) {
                    "Tim Testor"
@@ -33,7 +31,7 @@ class MessageHelperTest : RobolectricTest() {
                }
            }
        }
        contactsWithFakeSpoofContact = object : Contacts(context) {
        contactsWithFakeSpoofContact = object : Contacts(mock()) {
            override fun getNameFor(emailAddress: EmailAddress): String? {
                return if ("test@testor.com" == emailAddress.address) {
                    "Tim@Testor"
+3 −3
Original line number Diff line number Diff line
@@ -2,10 +2,10 @@ package com.fsck.k9.storage.messages

import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.android.common.database.getIntOrNull
import app.k9mail.core.android.common.database.getLongOrNull
import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.K9
import com.fsck.k9.helper.getIntOrNull
import com.fsck.k9.helper.getLongOrNull
import com.fsck.k9.helper.getStringOrNull
import com.fsck.k9.mailstore.LockableDatabase
import java.util.UUID
import timber.log.Timber
Loading