Loading app/core/src/main/java/com/fsck/k9/KoinModule.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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()) } Loading app/core/src/main/java/com/fsck/k9/helper/Contacts.kt +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 Loading @@ -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. Loading @@ -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. Loading @@ -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 } /** Loading @@ -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 } /** Loading @@ -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?>() /** Loading app/core/src/main/java/com/fsck/k9/mailstore/OutboxStateRepository.kt +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( Loading app/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt +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 Loading @@ -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() { Loading @@ -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" Loading @@ -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" Loading app/storage/src/main/java/com/fsck/k9/storage/messages/MoveMessageOperations.kt +3 −3 Original line number Diff line number Diff line Loading @@ -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 Loading
app/core/src/main/java/com/fsck/k9/KoinModule.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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()) } Loading
app/core/src/main/java/com/fsck/k9/helper/Contacts.kt +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 Loading @@ -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. Loading @@ -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. Loading @@ -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 } /** Loading @@ -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 } /** Loading @@ -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?>() /** Loading
app/core/src/main/java/com/fsck/k9/mailstore/OutboxStateRepository.kt +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( Loading
app/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt +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 Loading @@ -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() { Loading @@ -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" Loading @@ -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" Loading
app/storage/src/main/java/com/fsck/k9/storage/messages/MoveMessageOperations.kt +3 −3 Original line number Diff line number Diff line Loading @@ -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