diff --git a/app/autodiscovery/api/src/main/java/com/fsck/k9/autodiscovery/api/ConnectionSettingsDiscovery.kt b/app/autodiscovery/api/src/main/java/com/fsck/k9/autodiscovery/api/ConnectionSettingsDiscovery.kt index 90eb4b6f912e3c13f37e95aa98d29d76a0f12299..4ef3af580058340c7b4aaee296cf327f5a8b8dab 100644 --- a/app/autodiscovery/api/src/main/java/com/fsck/k9/autodiscovery/api/ConnectionSettingsDiscovery.kt +++ b/app/autodiscovery/api/src/main/java/com/fsck/k9/autodiscovery/api/ConnectionSettingsDiscovery.kt @@ -4,13 +4,7 @@ import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity interface ConnectionSettingsDiscovery { - fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? -} - -enum class DiscoveryTarget(val outgoing: Boolean, val incoming: Boolean) { - OUTGOING(true, false), - INCOMING(false, true), - INCOMING_AND_OUTGOING(true, true) + fun discover(email: String): DiscoveryResults? } data class DiscoveryResults(val incoming: List, val outgoing: List) diff --git a/app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt b/app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt index b2dd7bd2194b92bbdb7b2886a09f373a0dbc5292..fa8aff880a8dc325f9e168e0e978a47228a460b9 100644 --- a/app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt +++ b/app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt @@ -5,7 +5,6 @@ import android.net.Uri import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings import com.fsck.k9.autodiscovery.api.DiscoveryResults -import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.helper.EmailHelper import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity @@ -19,7 +18,7 @@ class ProvidersXmlDiscovery( private val oAuthConfigurationProvider: OAuthConfigurationProvider ) : ConnectionSettingsDiscovery { - override fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? { + override fun discover(email: String): DiscoveryResults? { val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val provider = findProviderForDomain(domain) ?: return null diff --git a/app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt b/app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt index 4e7d3f329e63388c252178e3466f0f30e90619a6..f91ce3b38d48d9522dddf1dbf13477aba3556626 100644 --- a/app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt +++ b/app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt @@ -2,7 +2,6 @@ package com.fsck.k9.autodiscovery.providersxml import androidx.test.core.app.ApplicationProvider import com.fsck.k9.RobolectricTest -import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity import com.fsck.k9.oauth.OAuthConfiguration @@ -17,7 +16,7 @@ class ProvidersXmlDiscoveryTest : RobolectricTest() { @Test fun discover_withGmailDomain_shouldReturnCorrectSettings() { - val connectionSettings = providersXmlDiscovery.discover("user@gmail.com", DiscoveryTarget.INCOMING_AND_OUTGOING) + val connectionSettings = providersXmlDiscovery.discover("user@gmail.com") assertThat(connectionSettings).isNotNull() with(connectionSettings!!.incoming.first()) { @@ -37,7 +36,7 @@ class ProvidersXmlDiscoveryTest : RobolectricTest() { @Test fun discover_withUnknownDomain_shouldReturnNull() { val connectionSettings = providersXmlDiscovery.discover( - "user@not.present.in.providers.xml.example", DiscoveryTarget.INCOMING_AND_OUTGOING + "user@not.present.in.providers.xml.example" ) assertThat(connectionSettings).isNull() diff --git a/app/autodiscovery/srvrecords/src/main/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscovery.kt b/app/autodiscovery/srvrecords/src/main/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscovery.kt index af8e2eed45631e34bde24e22d9445e81b3e38821..9dbbfdbd76d128170be4c42191d5aef3209f4485 100644 --- a/app/autodiscovery/srvrecords/src/main/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscovery.kt +++ b/app/autodiscovery/srvrecords/src/main/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscovery.kt @@ -3,7 +3,6 @@ package com.fsck.k9.autodiscovery.srvrecords import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings import com.fsck.k9.autodiscovery.api.DiscoveryResults -import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.helper.EmailHelper import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity @@ -12,19 +11,19 @@ class SrvServiceDiscovery( private val srvResolver: MiniDnsSrvResolver ) : ConnectionSettingsDiscovery { - override fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? { + override fun discover(email: String): DiscoveryResults? { val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val mailServicePriority = compareBy { it.priority }.thenByDescending { it.security } - val outgoingSettings = if (target.outgoing) - listOf(SrvType.SUBMISSIONS, SrvType.SUBMISSION).flatMap { srvResolver.lookup(domain, it) } - .sortedWith(mailServicePriority).map { newServerSettings(it, email) } - else listOf() + val outgoingSettings = listOf(SrvType.SUBMISSIONS, SrvType.SUBMISSION) + .flatMap { srvResolver.lookup(domain, it) } + .sortedWith(mailServicePriority) + .map { newServerSettings(it, email) } - val incomingSettings = if (target.incoming) - listOf(SrvType.IMAPS, SrvType.IMAP).flatMap { srvResolver.lookup(domain, it) } - .sortedWith(mailServicePriority).map { newServerSettings(it, email) } - else listOf() + val incomingSettings = listOf(SrvType.IMAPS, SrvType.IMAP) + .flatMap { srvResolver.lookup(domain, it) } + .sortedWith(mailServicePriority) + .map { newServerSettings(it, email) } return DiscoveryResults(incoming = incomingSettings, outgoing = outgoingSettings) } diff --git a/app/autodiscovery/srvrecords/src/test/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscoveryTest.kt b/app/autodiscovery/srvrecords/src/test/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscoveryTest.kt index 5123822d74784d1f60a902286ffda99a37de83bc..73425a50fff5b34c52cfc8108fe33ba1a9c92159 100644 --- a/app/autodiscovery/srvrecords/src/test/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscoveryTest.kt +++ b/app/autodiscovery/srvrecords/src/test/java/com/fsck/k9/autodiscovery/srvrecords/SrvServiceDiscoveryTest.kt @@ -1,15 +1,11 @@ package com.fsck.k9.autodiscovery.srvrecords -import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings import com.fsck.k9.autodiscovery.api.DiscoveryResults -import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.mail.ConnectionSecurity import org.junit.Assert.assertEquals import org.junit.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions class SrvServiceDiscoveryTest { @@ -18,7 +14,7 @@ class SrvServiceDiscoveryTest { val srvResolver = newMockSrvResolver() val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.INCOMING_AND_OUTGOING) + val result = srvServiceDiscovery.discover("test@example.com") assertEquals(DiscoveryResults(listOf(), listOf()), result) } @@ -33,7 +29,7 @@ class SrvServiceDiscoveryTest { ) val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.INCOMING_AND_OUTGOING) + val result = srvServiceDiscovery.discover("test@example.com") assertEquals(2, result!!.incoming.size) assertEquals(0, result.outgoing.size) @@ -57,7 +53,7 @@ class SrvServiceDiscoveryTest { ) val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.INCOMING_AND_OUTGOING) + val result = srvServiceDiscovery.discover("test@example.com") assertEquals(0, result!!.incoming.size) assertEquals(2, result.outgoing.size) @@ -133,7 +129,7 @@ class SrvServiceDiscoveryTest { ) val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.INCOMING_AND_OUTGOING) + val result = srvServiceDiscovery.discover("test@example.com") assertEquals( listOf( @@ -155,54 +151,6 @@ class SrvServiceDiscoveryTest { ) } - @Test - fun discover_whenOnlyOutgoingTrue_shouldOnlyFetchOutgoing() { - val srvResolver = newMockSrvResolver( - submissionServices = listOf( - newMailService( - host = "smtp.example.com", - port = 465, - srvType = SrvType.SUBMISSIONS, - security = ConnectionSecurity.SSL_TLS_REQUIRED, - priority = 0 - ) - ) - ) - - val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.OUTGOING) - - verify(srvResolver).lookup("example.com", SrvType.SUBMISSIONS) - verify(srvResolver).lookup("example.com", SrvType.SUBMISSION) - verifyNoMoreInteractions(srvResolver) - assertEquals(1, result!!.outgoing.size) - assertEquals(listOf(), result.incoming) - } - - @Test - fun discover_whenOnlyIncomingTrue_shouldOnlyFetchIncoming() { - val srvResolver = newMockSrvResolver( - imapsServices = listOf( - newMailService( - host = "imaps.example.com", - port = 993, - srvType = SrvType.IMAPS, - security = ConnectionSecurity.SSL_TLS_REQUIRED, - priority = 0 - ) - ) - ) - - val srvServiceDiscovery = SrvServiceDiscovery(srvResolver) - val result = srvServiceDiscovery.discover("test@example.com", DiscoveryTarget.INCOMING) - - verify(srvResolver).lookup("example.com", SrvType.IMAPS) - verify(srvResolver).lookup("example.com", SrvType.IMAP) - verifyNoMoreInteractions(srvResolver) - assertEquals(1, result!!.incoming.size) - assertEquals(listOf(), result.outgoing) - } - private fun newMailService( host: String = "example.com", priority: Int = 0, diff --git a/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigFetcher.kt b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigFetcher.kt index 94c513d550641dd8be92df58b09d38f95a145443..c4da63191c07fd8033d18ea7749294c131f9fbbf 100644 --- a/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigFetcher.kt +++ b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigFetcher.kt @@ -1,6 +1,5 @@ package com.fsck.k9.autodiscovery.thunderbird -import com.fsck.k9.helper.EmailHelper import java.io.InputStream import okhttp3.HttpUrl import okhttp3.OkHttpClient @@ -8,8 +7,7 @@ import okhttp3.Request class ThunderbirdAutoconfigFetcher(private val okHttpClient: OkHttpClient) { - fun fetchAutoconfigFile(email: String): InputStream? { - val url = getAutodiscoveryAddress(email) + fun fetchAutoconfigFile(url: HttpUrl): InputStream? { val request = Request.Builder().url(url).build() val response = okHttpClient.newCall(request).execute() @@ -20,20 +18,4 @@ class ThunderbirdAutoconfigFetcher(private val okHttpClient: OkHttpClient) { null } } - - companion object { - // address described at: - // https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration#Configuration_server_at_ISP - internal fun getAutodiscoveryAddress(email: String): HttpUrl { - val domain = EmailHelper.getDomainFromEmailAddress(email) - requireNotNull(domain) { "Couldn't extract domain from email address: $email" } - - return HttpUrl.Builder() - .scheme("https") - .host(domain) - .addEncodedPathSegments(".well-known/autoconfig/mail/config-v1.1.xml") - .addQueryParameter("emailaddress", email) - .build() - } - } } diff --git a/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProvider.kt b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..bffb1e20f3f941de68e5cf19d7525b9a74701e4e --- /dev/null +++ b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProvider.kt @@ -0,0 +1,47 @@ +package com.fsck.k9.autodiscovery.thunderbird + +import com.fsck.k9.helper.EmailHelper +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl + +class ThunderbirdAutoconfigUrlProvider { + fun getAutoconfigUrls(email: String): List { + val domain = EmailHelper.getDomainFromEmailAddress(email) + requireNotNull(domain) { "Couldn't extract domain from email address: $email" } + + return listOf( + createProviderUrl(domain, email), + createDomainUrl(scheme = "https", domain), + createDomainUrl(scheme = "http", domain), + createIspDbUrl(domain) + ) + } + + private fun createProviderUrl(domain: String?, email: String): HttpUrl { + // https://autoconfig.{domain}/mail/config-v1.1.xml?emailaddress={email} + return HttpUrl.Builder() + .scheme("https") + .host("autoconfig.$domain") + .addEncodedPathSegments("mail/config-v1.1.xml") + .addQueryParameter("emailaddress", email) + .build() + } + + private fun createDomainUrl(scheme: String, domain: String): HttpUrl { + // https://{domain}/.well-known/autoconfig/mail/config-v1.1.xml + // http://{domain}/.well-known/autoconfig/mail/config-v1.1.xml + return HttpUrl.Builder() + .scheme(scheme) + .host(domain) + .addEncodedPathSegments(".well-known/autoconfig/mail/config-v1.1.xml") + .build() + } + + private fun createIspDbUrl(domain: String): HttpUrl { + // https://autoconfig.thunderbird.net/v1.1/{domain} + return "https://autoconfig.thunderbird.net/v1.1/".toHttpUrl() + .newBuilder() + .addPathSegment(domain) + .build() + } +} diff --git a/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdDiscovery.kt b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdDiscovery.kt index ff02028f9c2708d6cddda9c79aae070291b19c18..23d16819ad75ec5391556bc7cb0836f504e828a1 100644 --- a/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdDiscovery.kt +++ b/app/autodiscovery/thunderbird/src/main/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdDiscovery.kt @@ -2,19 +2,26 @@ package com.fsck.k9.autodiscovery.thunderbird import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery import com.fsck.k9.autodiscovery.api.DiscoveryResults -import com.fsck.k9.autodiscovery.api.DiscoveryTarget class ThunderbirdDiscovery( + private val urlProvider: ThunderbirdAutoconfigUrlProvider, private val fetcher: ThunderbirdAutoconfigFetcher, private val parser: ThunderbirdAutoconfigParser ) : ConnectionSettingsDiscovery { - override fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? { - val autoconfigInputStream = fetcher.fetchAutoconfigFile(email) ?: return null + override fun discover(email: String): DiscoveryResults? { + val autoconfigUrls = urlProvider.getAutoconfigUrls(email) - return autoconfigInputStream.use { - parser.parseSettings(it, email) - } + return autoconfigUrls + .asSequence() + .mapNotNull { autoconfigUrl -> + fetcher.fetchAutoconfigFile(autoconfigUrl)?.use { inputStream -> + parser.parseSettings(inputStream, email) + } + } + .firstOrNull { result -> + result.incoming.isNotEmpty() || result.outgoing.isNotEmpty() + } } override fun toString(): String = "Thunderbird autoconfig" diff --git a/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigTest.kt b/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigTest.kt index 4648f675605391b09c4e6d4c8c88b631b29a2974..ca7dc0f1c2d6329e73c1758dbf18a3d98aabb08c 100644 --- a/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigTest.kt +++ b/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigTest.kt @@ -169,13 +169,4 @@ class ThunderbirdAutoconfigTest { ) ) } - - @Test - fun generatedUrls() { - val autoDiscoveryAddress = ThunderbirdAutoconfigFetcher.getAutodiscoveryAddress("test@metacode.biz") - - assertThat(autoDiscoveryAddress.toString()).isEqualTo( - "https://metacode.biz/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test%40metacode.biz" - ) - } } diff --git a/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProviderTest.kt b/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProviderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..d285a3ac02f2ce94d3f2c03c9373e2d00e38d288 --- /dev/null +++ b/app/autodiscovery/thunderbird/src/test/java/com/fsck/k9/autodiscovery/thunderbird/ThunderbirdAutoconfigUrlProviderTest.kt @@ -0,0 +1,20 @@ +package com.fsck.k9.autodiscovery.thunderbird + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ThunderbirdAutoconfigUrlProviderTest { + private val urlProvider = ThunderbirdAutoconfigUrlProvider() + + @Test + fun `getAutoconfigUrls with ASCII email address`() { + val autoconfigUrls = urlProvider.getAutoconfigUrls("test@domain.example") + + assertThat(autoconfigUrls.map { it.toString() }).containsExactly( + "https://autoconfig.domain.example/mail/config-v1.1.xml?emailaddress=test%40domain.example", + "https://domain.example/.well-known/autoconfig/mail/config-v1.1.xml", + "http://domain.example/.well-known/autoconfig/mail/config-v1.1.xml", + "https://autoconfig.thunderbird.net/v1.1/domain.example" + ) + } +} diff --git a/app/core/src/main/java/com/fsck/k9/K9.kt b/app/core/src/main/java/com/fsck/k9/K9.kt index af9c8e7a28a35ee290f7fb29074499893a5c651e..fb5af082afc2edb03371a9483258c16dfa13c8dc 100644 --- a/app/core/src/main/java/com/fsck/k9/K9.kt +++ b/app/core/src/main/java/com/fsck/k9/K9.kt @@ -123,7 +123,7 @@ object K9 : EarlyInit { val fontSizes = FontSizes() @JvmStatic - var backgroundOps = BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC + var backgroundOps = BACKGROUND_OPS.ALWAYS @JvmStatic var isShowAnimations = true @@ -266,6 +266,12 @@ object K9 : EarlyInit { @JvmStatic var pgpSignOnlyDialogCounter: Int = 0 + @JvmStatic + var swipeRightAction: SwipeAction = SwipeAction.ToggleSelection + + @JvmStatic + var swipeLeftAction: SwipeAction = SwipeAction.ToggleRead + val isQuietTime: Boolean get() { if (!isQuietTimeEnabled) { @@ -362,7 +368,7 @@ object K9 : EarlyInit { isThreadedViewEnabled = storage.getBoolean("threadedView", true) fontSizes.load(storage) - backgroundOps = storage.getEnum("backgroundOperations", BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC) + backgroundOps = storage.getEnum("backgroundOperations", BACKGROUND_OPS.ALWAYS) isColorizeMissingContactPictures = storage.getBoolean("colorizeMissingContactPictures", true) @@ -382,6 +388,9 @@ object K9 : EarlyInit { messageViewTheme = storage.getEnum("messageViewTheme", SubTheme.USE_GLOBAL) messageComposeTheme = storage.getEnum("messageComposeTheme", SubTheme.USE_GLOBAL) isFixedMessageViewTheme = storage.getBoolean("fixedMessageViewTheme", true) + + swipeRightAction = storage.getEnum("swipeRightAction", SwipeAction.ToggleSelection) + swipeLeftAction = storage.getEnum("swipeLeftAction", SwipeAction.ToggleRead) } internal fun save(editor: StorageEditor) { @@ -447,6 +456,9 @@ object K9 : EarlyInit { editor.putInt("pgpInlineDialogCounter", pgpInlineDialogCounter) editor.putInt("pgpSignOnlyDialogCounter", pgpSignOnlyDialogCounter) + editor.putEnum("swipeRightAction", swipeRightAction) + editor.putEnum("swipeLeftAction", swipeLeftAction) + fontSizes.save(editor) } diff --git a/app/core/src/main/java/com/fsck/k9/Preferences.kt b/app/core/src/main/java/com/fsck/k9/Preferences.kt index 7e66d18f85a32b87588b3f0038f855898e38fc2c..84517c4cf03f0f29b564bfb83fda1d1edf7896a6 100644 --- a/app/core/src/main/java/com/fsck/k9/Preferences.kt +++ b/app/core/src/main/java/com/fsck/k9/Preferences.kt @@ -110,6 +110,9 @@ class Preferences internal constructor( } } + private val completeAccounts: List + get() = accounts.filter { it.isFinishedSetup } + override fun getAccount(accountUuid: String): Account? { synchronized(accountLock) { if (accountsMap == null) { @@ -151,10 +154,10 @@ class Preferences internal constructor( @OptIn(ExperimentalCoroutinesApi::class) override fun getAccountsFlow(): Flow> { return callbackFlow { - send(accounts) + send(completeAccounts) val listener = AccountsChangeListener { - trySendBlocking(accounts) + trySendBlocking(completeAccounts) } addOnAccountsChangeListener(listener) diff --git a/app/core/src/main/java/com/fsck/k9/SwipeAction.kt b/app/core/src/main/java/com/fsck/k9/SwipeAction.kt new file mode 100644 index 0000000000000000000000000000000000000000..8079a8766153cb12e08a15d9078771a2614ddf28 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/SwipeAction.kt @@ -0,0 +1,12 @@ +package com.fsck.k9 + +enum class SwipeAction(val removesItem: Boolean) { + None(removesItem = false), + ToggleSelection(removesItem = false), + ToggleRead(removesItem = false), + ToggleStar(removesItem = false), + Archive(removesItem = true), + Delete(removesItem = true), + Spam(removesItem = true), + Move(removesItem = true) +} diff --git a/app/core/src/main/java/com/fsck/k9/controller/DraftOperations.kt b/app/core/src/main/java/com/fsck/k9/controller/DraftOperations.kt index b613b650e63b4e2e91329ae67d88a9063736444b..83963f052d2456536c4b0f3c19b1a01e66e8de8b 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/DraftOperations.kt +++ b/app/core/src/main/java/com/fsck/k9/controller/DraftOperations.kt @@ -75,8 +75,10 @@ internal class DraftOperations( messagingController.queuePendingCommand(account, command) } else { val fakeMessageServerId = messageStore.getMessageServerId(messageId) - val command = PendingAppend.create(folderId, fakeMessageServerId) - messagingController.queuePendingCommand(account, command) + if (fakeMessageServerId != null) { + val command = PendingAppend.create(folderId, fakeMessageServerId) + messagingController.queuePendingCommand(account, command) + } } messagingController.processPendingCommands(account) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 4d56037172418a8f4a09353f2f3119307f9bfcc6..5057e12d503092a1956f4cb8264b14d15cbfafa5 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -93,7 +93,6 @@ import static com.fsck.k9.K9.MAX_SEND_ATTEMPTS; import static com.fsck.k9.helper.ExceptionHelper.getRootCauseMessage; import static com.fsck.k9.helper.Preconditions.checkNotNull; import static com.fsck.k9.mail.Flag.X_REMOTE_COPY_STARTED; -import static com.fsck.k9.search.LocalSearchExtensions.getAccountsFromLocalSearch; /** @@ -395,27 +394,6 @@ public class MessagingController { } } - /** - * Find all messages in any local account which match the query 'query' - */ - public List searchLocalMessages(final LocalSearch search) { - List searchAccounts = getAccountsFromLocalSearch(search, preferences); - - List messages = new ArrayList<>(); - for (final Account account : searchAccounts) { - try { - LocalStore localStore = localStoreProvider.getInstance(account); - List localMessages = localStore.searchForMessages(search); - - messages.addAll(localMessages); - } catch (Exception e) { - Timber.e(e); - } - } - - return messages; - } - public Future searchRemoteMessages(String acctUuid, long folderId, String query, Set requiredFlags, Set forbiddenFlags, MessagingListener listener) { Timber.i("searchRemoteMessages (acct = %s, folderId = %d, query = %s)", acctUuid, folderId, query); @@ -1663,9 +1641,11 @@ public class MessagingController { if (!sentFolder.isLocalOnly()) { String destinationUid = messageStore.getMessageServerId(destinationMessageId); - PendingCommand command = PendingAppend.create(sentFolderId, destinationUid); - queuePendingCommand(account, command); - processPendingCommands(account); + if (destinationUid != null) { + PendingCommand command = PendingAppend.create(sentFolderId, destinationUid); + queuePendingCommand(account, command); + processPendingCommands(account); + } } } @@ -1949,9 +1929,10 @@ public class MessagingController { MessageStore messageStore = messageStoreManager.getMessageStore(account); String messageServerId = messageStore.getMessageServerId(messageId); - MessageReference messageReference = new MessageReference(account.getUuid(), folderId, messageServerId); - - deleteMessage(messageReference); + if (messageServerId != null) { + MessageReference messageReference = new MessageReference(account.getUuid(), folderId, messageServerId); + deleteMessage(messageReference); + } } public void deleteThreads(final List messages) { @@ -2707,7 +2688,6 @@ public class MessagingController { LocalFolder localFolder = message.getFolder(); if (!suppressNotifications && notificationStrategy.shouldNotifyForMessage(account, localFolder, message, isOldMessage)) { - Timber.v("Creating notification for message %s:%s", localFolder.getName(), message.getUid()); // Notify with the localMessage so that we don't have to recalculate the content preview. boolean silent = notificationState.wasNotified(); notificationController.addNewMailNotification(account, message, silent); diff --git a/app/core/src/main/java/com/fsck/k9/helper/CursorExtensions.kt b/app/core/src/main/java/com/fsck/k9/helper/CursorExtensions.kt index 61c60c7ba82299853f364239f771c23ab7462413..53544cdf9ea8a42a4b14b916d37ca2379e65b936 100644 --- a/app/core/src/main/java/com/fsck/k9/helper/CursorExtensions.kt +++ b/app/core/src/main/java/com/fsck/k9/helper/CursorExtensions.kt @@ -35,3 +35,7 @@ fun Cursor.getIntOrThrow(columnName: String): Int { fun Cursor.getLongOrThrow(columnName: String): Long { return getLongOrNull(columnName) ?: error("Column $columnName must not be null") } + +fun Cursor.getBoolean(columnIndex: Int): Boolean { + return getString(columnIndex).toBoolean() +} diff --git a/app/core/src/main/java/com/fsck/k9/helper/DefaultTrustedSocketFactory.java b/app/core/src/main/java/com/fsck/k9/helper/DefaultTrustedSocketFactory.java index ce046daa9e1e5a93f251f341b6ae99b192f9c93b..7ab0b258c9601962c3e759acceafe0dbc9fa3e15 100644 --- a/app/core/src/main/java/com/fsck/k9/helper/DefaultTrustedSocketFactory.java +++ b/app/core/src/main/java/com/fsck/k9/helper/DefaultTrustedSocketFactory.java @@ -11,26 +11,23 @@ import java.util.List; import android.content.Context; import android.net.SSLCertificateSocketFactory; +import android.os.Build; import android.text.TextUtils; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.ssl.TrustManagerFactory; import com.fsck.k9.mail.ssl.TrustedSocketFactory; import javax.net.ssl.KeyManager; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import timber.log.Timber; -/** - * Prior to API 21 (and notably from API 10 - 2.3.4) Android weakened it's cipher list - * by ordering them badly such that RC4-MD5 was preferred. To work around this we - * remove the insecure ciphers and reorder them so the latest more secure ciphers are at the top. - * - * On more modern versions of Android we keep the system configuration. - */ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { private static final String[] ENABLED_CIPHERS; private static final String[] ENABLED_PROTOCOLS; @@ -150,6 +147,11 @@ public class DefaultTrustedSocketFactory implements TrustedSocketFactory { if (factory instanceof android.net.SSLCertificateSocketFactory) { SSLCertificateSocketFactory sslCertificateSocketFactory = (SSLCertificateSocketFactory) factory; sslCertificateSocketFactory.setHostname(socket, hostname); + } else if (Build.VERSION.SDK_INT >= 24) { + SSLParameters sslParameters = socket.getSSLParameters(); + List sniServerNames = Collections.singletonList(new SNIHostName(hostname)); + sslParameters.setServerNames(sniServerNames); + socket.setSSLParameters(sslParameters); } else { setHostnameViaReflection(socket, hostname); } diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalMessage.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalMessage.java index c3d0de6613b61d26aaf01e263ca96205f5d0dddf..a6381396728ed737977069060fad085e29050121 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalMessage.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalMessage.java @@ -194,10 +194,6 @@ public class LocalMessage extends MimeMessage { return (attachmentCount > 0); } - public int getAttachmentCount() { - return attachmentCount; - } - @Override public void setFrom(Address from) { this.mFrom = new Address[] { from }; @@ -388,10 +384,6 @@ public class LocalMessage extends MimeMessage { return mFolder; } - public String getUri() { - return "k9mail://messages/" + getAccount().getAccountNumber() + "/" + getFolder().getDatabaseId() + "/" + getUid(); - } - @Override public void writeTo(OutputStream out) throws IOException, MessagingException { if (headerNeedsUpdating) { diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/MessageListRepository.kt b/app/core/src/main/java/com/fsck/k9/mailstore/MessageListRepository.kt index 5a748ee2728e85dfabc2d99f1fda6b004e019330..2bea1c5340779581f540848aa56542a34cd52ee3 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/MessageListRepository.kt +++ b/app/core/src/main/java/com/fsck/k9/mailstore/MessageListRepository.kt @@ -5,19 +5,32 @@ import java.util.concurrent.CopyOnWriteArraySet class MessageListRepository( private val messageStoreManager: MessageStoreManager ) { - private val listeners = CopyOnWriteArraySet>() + private val globalListeners = CopyOnWriteArraySet() + private val accountListeners = CopyOnWriteArraySet>() + + fun addListener(listener: MessageListChangedListener) { + globalListeners.add(listener) + } fun addListener(accountUuid: String, listener: MessageListChangedListener) { - listeners.add(accountUuid to listener) + accountListeners.add(accountUuid to listener) } fun removeListener(listener: MessageListChangedListener) { - val entries = listeners.filter { it.second == listener }.toSet() - listeners.removeAll(entries) + globalListeners.remove(listener) + + val accountEntries = accountListeners.filter { it.second == listener }.toSet() + if (accountEntries.isNotEmpty()) { + accountListeners.removeAll(accountEntries) + } } fun notifyMessageListChanged(accountUuid: String) { - for (listener in listeners) { + for (listener in globalListeners) { + listener.onMessageListChanged() + } + + for (listener in accountListeners) { if (listener.first == accountUuid) { listener.second.onMessageListChanged() } diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt b/app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt index eecbbd96454ac25f4a38f8b6dd2aa8b1911114f3..80d3dd32bc2d30096ddea89c4c4ddfd7dbee370b 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt +++ b/app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt @@ -91,7 +91,7 @@ interface MessageStore { /** * Retrieve the server ID for a given message. */ - fun getMessageServerId(messageId: Long): String + fun getMessageServerId(messageId: Long): String? /** * Retrieve the server IDs for the given messages. diff --git a/app/core/src/main/java/com/fsck/k9/message/TextBodyBuilder.java b/app/core/src/main/java/com/fsck/k9/message/TextBodyBuilder.java index eb2235cb1bf75c9b2b43c486c226ede6ab66881a..5a03104c48e77138a062821696505a1213548de2 100644 --- a/app/core/src/main/java/com/fsck/k9/message/TextBodyBuilder.java +++ b/app/core/src/main/java/com/fsck/k9/message/TextBodyBuilder.java @@ -188,8 +188,7 @@ class TextBodyBuilder { private String getSignatureHtml() { String signature = ""; if (!isEmpty(mSignature)) { - signature = "
" + - HtmlConverter.textToHtmlFragmentWithOriginalWhitespace(mSignature) + "
"; + signature = HtmlConverter.textToHtmlFragment(mSignature); } return signature; } diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt index fe0144108e8d4c800d930123bb743c28811318d4..6d506852ee496ff63b78c3ed04b8bb57d90d4489 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt @@ -4,6 +4,7 @@ import com.fsck.k9.Account import com.fsck.k9.controller.MessageReference import com.fsck.k9.mailstore.LocalFolder import com.fsck.k9.mailstore.LocalMessage +import timber.log.Timber class NotificationController internal constructor( private val certificateErrorNotificationController: CertificateErrorNotificationController, @@ -61,20 +62,33 @@ class NotificationController internal constructor( } fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) { + Timber.v( + "Creating notification for message %s:%s:%s", + message.account.uuid, + message.folder.databaseId, + message.uid + ) + newMailNotificationController.addNewMailNotification(account, message, silent) } fun removeNewMailNotification(account: Account, messageReference: MessageReference) { + Timber.v("Removing notification for message %s", messageReference) + newMailNotificationController.removeNewMailNotifications(account, clearNewMessageState = true) { listOf(messageReference) } } fun clearNewMailNotifications(account: Account, selector: (List) -> List) { + Timber.v("Removing some notifications for account %s", account.uuid) + newMailNotificationController.removeNewMailNotifications(account, clearNewMessageState = false, selector) } fun clearNewMailNotifications(account: Account, clearNewMessageState: Boolean) { + Timber.v("Removing all notifications for account %s", account.uuid) + newMailNotificationController.clearNewMailNotifications(account, clearNewMessageState) } } diff --git a/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java b/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java index 2458bf7800227ec7d3cd9fc887df22dce2f0eaaf..93dd1d23ca022a4e5a0b049899ab4783f23d98d7 100644 --- a/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java +++ b/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java @@ -10,15 +10,16 @@ import java.util.Set; import java.util.TreeMap; import android.content.Context; -import android.graphics.Color; import com.fsck.k9.Account; import com.fsck.k9.Account.SortType; import com.fsck.k9.DI; import com.fsck.k9.FontSizes; import com.fsck.k9.K9; +import com.fsck.k9.K9.BACKGROUND_OPS; import com.fsck.k9.K9.NotificationQuickDelete; import com.fsck.k9.K9.SplitViewMode; +import com.fsck.k9.SwipeAction; import com.fsck.k9.core.R; import com.fsck.k9.preferences.Settings.BooleanSetting; import com.fsck.k9.preferences.Settings.ColorSetting; @@ -51,7 +52,8 @@ public class GeneralSettingsDescriptions { new V(1, new BooleanSetting(false)) )); s.put("backgroundOperations", Settings.versions( - new V(1, new EnumSetting<>(K9.BACKGROUND_OPS.class, K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC)) + new V(1, new EnumSetting<>(K9.BACKGROUND_OPS.class, K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC)), + new V(83, new EnumSetting<>(K9.BACKGROUND_OPS.class, BACKGROUND_OPS.ALWAYS)) )); s.put("changeRegisteredNameColor", Settings.versions( new V(1, new BooleanSetting(false)) @@ -275,6 +277,12 @@ public class GeneralSettingsDescriptions { s.put("showStarredCount", Settings.versions( new V(75, new BooleanSetting(false)) )); + s.put("swipeRightAction", Settings.versions( + new V(83, new EnumSetting<>(SwipeAction.class, SwipeAction.ToggleSelection)) + )); + s.put("swipeLeftAction", Settings.versions( + new V(83, new EnumSetting<>(SwipeAction.class, SwipeAction.ToggleRead)) + )); SETTINGS = Collections.unmodifiableMap(s); diff --git a/app/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt b/app/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt index 93b5ed8699d4d550b59d908f9ab19c3ef35a27d6..adb2c53ff839f60e07c40f5ae5f63502c7340737 100644 --- a/app/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt +++ b/app/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt @@ -40,6 +40,9 @@ internal class RealGeneralSettingsManager( } override fun getSettingsFlow(): Flow { + // Make sure to load settings now if they haven't been loaded already. This will also update settingsFlow. + getSettings() + return settingsFlow.distinctUntilChanged() } diff --git a/app/core/src/main/java/com/fsck/k9/preferences/Settings.java b/app/core/src/main/java/com/fsck/k9/preferences/Settings.java index a82e888a7b22bf14bc31a50b184bd50efd0fdc08..a761d6fa90cb69eadd005e582b393089d1b33c9d 100644 --- a/app/core/src/main/java/com/fsck/k9/preferences/Settings.java +++ b/app/core/src/main/java/com/fsck/k9/preferences/Settings.java @@ -36,7 +36,7 @@ public class Settings { * * @see SettingsExporter */ - public static final int VERSION = 82; + public static final int VERSION = 83; static Map validate(int version, Map> settings, Map importedSettings, boolean useDefaultValues) { diff --git a/app/core/src/main/res/values/arrays_account_settings_values.xml b/app/core/src/main/res/values/arrays_account_settings_values.xml index b43b3c62256b5bbf4ace1e9dd0c33e7deb925087..d20d463a1e0787ebaea6d654532598b90dc67514 100644 --- a/app/core/src/main/res/values/arrays_account_settings_values.xml +++ b/app/core/src/main/res/values/arrays_account_settings_values.xml @@ -6,6 +6,10 @@ 0xFF0086FF + + 0xFF0086FF + + -1 5 diff --git a/app/core/src/main/res/values/arrays_general_settings_values.xml b/app/core/src/main/res/values/arrays_general_settings_values.xml index b5b0f49f67a8e32e5e1775010113f62724f54a21..95fca883f9a8e088db0092e1c61324e4b3d0d546 100644 --- a/app/core/src/main/res/values/arrays_general_settings_values.xml +++ b/app/core/src/main/res/values/arrays_general_settings_values.xml @@ -210,4 +210,15 @@ spam + + none + toggle_selection + toggle_read + toggle_star + archive + delete + spam + move + + diff --git a/app/core/src/main/res/values/material_colors.xml b/app/core/src/main/res/values/material_colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..31c59aac5bc4bbb91523431f863f3bb6020a22a3 --- /dev/null +++ b/app/core/src/main/res/values/material_colors.xml @@ -0,0 +1,211 @@ + + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + + #E1F5FE + #B3E5FC + #81D4FA + #4FC3F7 + #29B6F6 + #03A9F4 + #039BE5 + #0288D1 + #0277BD + #01579B + + #E8F5E9 + #C8E6C9 + #A5D6A7 + #81C784 + #66BB6A + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + + #ECEFF1 + #CFD8DC + #B0BEC5 + #90A4AE + #78909C + #607D8B + #546E7A + #455A64 + #37474F + #263238 + + #FCE4EC + #F8BBD0 + #F48FB1 + #F06292 + #EC407A + #E91E63 + #D81B60 + #C2185B + #AD1457 + #880E4F + + #E8EAF6 + #C5CAE9 + #9FA8DA + #7986CB + #5C6BC0 + #3F51B5 + #3949AB + #303F9F + #283593 + #1A237E + + #E0F7FA + #B2EBF2 + #80DEEA + #4DD0E1 + #26C6DA + #00BCD4 + #00ACC1 + #0097A7 + #00838F + #006064 + + #F1F8E9 + #DCEDC8 + #C5E1A5 + #AED581 + #9CCC65 + #8BC34A + #7CB342 + #689F38 + #558B2F + #33691E + + #FFF8E1 + #FFECB3 + #FFE082 + #FFD54F + #FFCA28 + #FFC107 + #FFB300 + #FFA000 + #FF8F00 + #FF6F00 + + #EFEBE9 + #D7CCC8 + #BCAAA4 + #A1887F + #8D6E63 + #795548 + #6D4C41 + #5D4037 + #4E342E + #3E2723 + + #F3E5F5 + #E1BEE7 + #CE93D8 + #BA68C8 + #AB47BC + #9C27B0 + #8E24AA + #7B1FA2 + #6A1B9A + #4A148C + + #E3F2FD + #BBDEFB + #90CAF9 + #64B5F6 + #42A5F5 + #2196F3 + #1E88E5 + #1976D2 + #1565C0 + #0D47A1 + + #E0F2F1 + #B2DFDB + #80CBC4 + #4DB6AC + #26A69A + #009688 + #00897B + #00796B + #00695C + #004D40 + + #F9FBE7 + #F0F4C3 + #E6EE9C + #DCE775 + #D4E157 + #CDDC39 + #C0CA33 + #AFB42B + #9E9D24 + #827717 + + #FFF3E0 + #FFE0B2 + #FFCC80 + #FFB74D + #FFA726 + #FF9800 + #FB8C00 + #F57C00 + #EF6C00 + #E65100 + + #FAFAFA + #F5F5F5 + #EEEEEE + #E0E0E0 + #BDBDBD + #9E9E9E + #757575 + #616161 + #424242 + #212121 + diff --git a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java index 641dbc62e0831b98a96a487dfda7355f53064459..1239d581f6ce9225d4f714270e9d1d54d18e9d3a 100644 --- a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java +++ b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java @@ -19,7 +19,6 @@ import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.CertificateValidationException; import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.Flag; -import com.fsck.k9.mail.MessageRetrievalListener; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mailstore.LocalFolder; @@ -35,15 +34,12 @@ import com.fsck.k9.mailstore.SpecialLocalFoldersCreator; import com.fsck.k9.notification.NotificationController; import com.fsck.k9.notification.NotificationStrategy; import com.fsck.k9.preferences.Protocols; -import com.fsck.k9.search.LocalSearch; import com.fsck.k9.search.SearchAccount; import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; -import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -52,7 +48,6 @@ import org.mockito.stubbing.Answer; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; -import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.eq; @@ -90,8 +85,6 @@ public class MessagingControllerTest extends K9RobolectricTest { @Mock private SimpleMessagingListener listener; @Mock - private LocalSearch search; - @Mock private LocalFolder localFolder; @Mock private LocalFolder sentFolder; @@ -101,8 +94,6 @@ public class MessagingControllerTest extends K9RobolectricTest { private NotificationController notificationController; @Mock private NotificationStrategy notificationStrategy; - @Captor - private ArgumentCaptor> messageRetrievalListenerCaptor; private Context appContext; private Set reqFlags; @@ -178,20 +169,6 @@ public class MessagingControllerTest extends K9RobolectricTest { verify(backend).refreshFolderList(); } - @Test - public void searchLocalMessages_shouldIgnoreExceptions() - throws Exception { - LocalMessage localMessage = mock(LocalMessage.class); - when(localMessage.getFolder()).thenReturn(localFolder); - when(search.searchAllAccounts()).thenReturn(true); - when(search.getAccountUuids()).thenReturn(new String[0]); - when(localStore.searchForMessages(search)).thenThrow(new MessagingException("Test")); - - List messages = controller.searchLocalMessages(search); - - assertThat(messages).isEmpty(); - } - private void setupRemoteSearch() throws Exception { remoteMessages = new ArrayList<>(); Collections.addAll(remoteMessages, "oldMessageUid", "newMessageUid1", "newMessageUid2"); diff --git a/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt b/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt index e29ac44ce0765d737a14002a707930657fb3c732..67eafbbef77f7a2c0d734c89ea980825ac162fc0 100644 --- a/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt +++ b/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt @@ -18,8 +18,8 @@ class TextBodyBuilderTest(val testData: TestData) { private const val QUOTED_HTML_TAGS_END = "\n" private const val QUOTED_HTML_TAGS_START = "" private const val SIGNATURE_TEXT = "-- \r\n\r\nsignature\r\n indented second line" - private const val SIGNATURE_TEXT_HTML = "
" + - "
--

signature
indented second line
" + private const val SIGNATURE_TEXT_HTML = + "
--

signature
\u00A0 indented second line
" @JvmStatic @Parameterized.Parameters(name = "{index}: {0}") diff --git a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt index 3bc44effdc79631440bcf5ee8ef46c879d7782fe..43a6d82738529e971a6d73ac72eafaeecc34eeee 100644 --- a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt +++ b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt @@ -13,7 +13,7 @@ internal class BodyCleaner { init { val allowList = Safelist.relaxed() - .addTags("font", "hr", "ins", "del", "center", "map", "area", "title") + .addTags("font", "hr", "ins", "del", "center", "map", "area", "title", "tt", "kbd", "samp", "var") .addAttributes("font", "color", "face", "size") .addAttributes("a", "name") .addAttributes("div", "align") diff --git a/app/html-cleaner/src/test/java/app/k9mail/html/cleaner/HtmlSanitizerTest.kt b/app/html-cleaner/src/test/java/app/k9mail/html/cleaner/HtmlSanitizerTest.kt index 8720a3273d5f9dad9e5a210cc6f6b2eeecc9c7eb..d60ae8b381240b6354c8ed3f3370845143c688bb 100644 --- a/app/html-cleaner/src/test/java/app/k9mail/html/cleaner/HtmlSanitizerTest.kt +++ b/app/html-cleaner/src/test/java/app/k9mail/html/cleaner/HtmlSanitizerTest.kt @@ -445,6 +445,43 @@ class HtmlSanitizerTest { ) } + @Test + fun `should keep 'tt' element`() { + assertTagsNotStripped("tt") + } + + @Test + fun `should keep 'kbd' element`() { + assertTagsNotStripped("kbd") + } + + @Test + fun `should keep 'samp' element`() { + assertTagsNotStripped("samp") + } + + @Test + fun `should keep 'var' element`() { + assertTagsNotStripped("var") + } + + private fun assertTagsNotStripped(element: String) { + val html = """<$element>some text""" + + val result = htmlSanitizer.sanitize(html) + + assertThat(result.toCompactString()).isEqualTo( + """ + + + + <$element>some text + + + """.trimIndent().trimLineBreaks() + ) + } + private fun Document.toCompactString(): String { outputSettings() .prettyPrint(false) diff --git a/app/k9mail-jmap/src/main/res/values/themes.xml b/app/k9mail-jmap/src/main/res/values/themes.xml index 8be56e875581657cfc3379c9eed99c0f948d1ae4..4122384ced4fafa99ca3ca187a5eaa2e2217caf3 100644 --- a/app/k9mail-jmap/src/main/res/values/themes.xml +++ b/app/k9mail-jmap/src/main/res/values/themes.xml @@ -12,9 +12,9 @@ + - diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle index f1bcc112c2a647dcbfef01708d0078dce314bf3b..ccc8764126e2d7128b2325f45739413b738c3d36 100644 --- a/app/k9mail/build.gradle +++ b/app/k9mail/build.gradle @@ -7,6 +7,7 @@ if (rootProject.testCoverage) { dependencies { implementation project(":app:ui:legacy") + implementation project(":app:ui:message-list-widget") implementation project(":app:core") implementation project(":app:storage") implementation project(":app:crypto-openpgp") @@ -52,8 +53,8 @@ android { applicationId "foundation.e.mail" testApplicationId "foundation.e.mail.tests" - versionCode 33007 - versionName '6.307' + versionCode 34000 + versionName '6.400' // Keep in sync with the resource string array 'supported_languages' resConfigs "in", "br", "ca", "cs", "cy", "da", "de", "et", "en", "en_GB", "es", "eo", "eu", "fr", "gd", "gl", diff --git a/app/k9mail/src/main/AndroidManifest.xml b/app/k9mail/src/main/AndroidManifest.xml index ccb5349bc302c3b686a21f606a3aee4184461e32..c6fccb39d3025881daa275b4cd4a69d76cb50a26 100644 --- a/app/k9mail/src/main/AndroidManifest.xml +++ b/app/k9mail/src/main/AndroidManifest.xml @@ -60,13 +60,6 @@ - - - - @@ -159,13 +152,6 @@ android:label="@string/ac_transfer_title" /> - - - - - - @@ -393,11 +374,6 @@ - - messagingController.addListener(listener) diff --git a/app/k9mail/src/main/java/com/fsck/k9/Dependencies.kt b/app/k9mail/src/main/java/com/fsck/k9/Dependencies.kt index 4f3aeb00ab8c8fab1943c03fbac51732709391ad..8967c972a600e2eeb1cd4754f885f7791c63f091 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/Dependencies.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/Dependencies.kt @@ -1,5 +1,6 @@ package com.fsck.k9 +import app.k9mail.ui.widget.list.messageListWidgetModule import com.fsck.k9.auth.createOAuthConfigurationProvider import com.fsck.k9.backends.backendsModule import com.fsck.k9.controller.ControllerExtension @@ -10,8 +11,7 @@ import com.fsck.k9.preferences.K9StoragePersister import com.fsck.k9.preferences.StoragePersister import com.fsck.k9.resources.resourcesModule import com.fsck.k9.storage.storageModule -import com.fsck.k9.widget.list.MessageListWidgetUpdateListener -import com.fsck.k9.widget.list.messageListWidgetModule +import com.fsck.k9.widget.list.messageListWidgetConfigModule import com.fsck.k9.widget.unread.UnreadWidgetUpdateListener import com.fsck.k9.widget.unread.unreadWidgetModule import org.koin.core.qualifier.named @@ -23,7 +23,6 @@ private val mainAppModule = module { MessagingListenerProvider( listOf( get(), - get() ) ) } @@ -35,6 +34,7 @@ private val mainAppModule = module { val appModules = listOf( mainAppModule, + messageListWidgetConfigModule, messageListWidgetModule, unreadWidgetModule, notificationModule, diff --git a/app/k9mail/src/main/java/com/fsck/k9/backends/WebDavBackendFactory.kt b/app/k9mail/src/main/java/com/fsck/k9/backends/WebDavBackendFactory.kt index 0e4e588e335a1fcda65b1ef11ceeb42b106aff68..f8bf8f7bfccb55cbfae8101fe41dad2e9a076990 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/backends/WebDavBackendFactory.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/backends/WebDavBackendFactory.kt @@ -8,7 +8,6 @@ import com.fsck.k9.mail.ssl.TrustManagerFactory import com.fsck.k9.mail.store.webdav.DraftsFolderProvider import com.fsck.k9.mail.store.webdav.SniHostSetter import com.fsck.k9.mail.store.webdav.WebDavStore -import com.fsck.k9.mail.transport.WebDavTransport import com.fsck.k9.mailstore.FolderRepository import com.fsck.k9.mailstore.K9BackendStorageFactory @@ -24,8 +23,7 @@ class WebDavBackendFactory( val serverSettings = account.incomingServerSettings val draftsFolderProvider = createDraftsFolderProvider(account) val webDavStore = WebDavStore(trustManagerFactory, sniHostSetter, serverSettings, draftsFolderProvider) - val webDavTransport = WebDavTransport(trustManagerFactory, sniHostSetter, serverSettings, draftsFolderProvider) - return WebDavBackend(accountName, backendStorage, webDavStore, webDavTransport) + return WebDavBackend(accountName, backendStorage, webDavStore) } private fun createDraftsFolderProvider(account: Account): DraftsFolderProvider { diff --git a/app/k9mail/src/main/java/com/fsck/k9/external/MessageInfoHolder.java b/app/k9mail/src/main/java/com/fsck/k9/external/MessageInfoHolder.java deleted file mode 100644 index 16f7bc573a9d0869f1306a4426c3e347ea9d2954..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/external/MessageInfoHolder.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.fsck.k9.external; - - -import java.util.Date; - -import android.content.Context; -import android.text.SpannableStringBuilder; - -import com.fsck.k9.Account; -import com.fsck.k9.K9; -import com.fsck.k9.helper.Contacts; -import com.fsck.k9.helper.MessageHelper; -import com.fsck.k9.mail.Address; -import com.fsck.k9.mail.Flag; -import com.fsck.k9.mail.Message.RecipientType; -import com.fsck.k9.mailstore.LocalMessage; -import com.fsck.k9.ui.R; - -class MessageInfoHolder { - public Date compareDate; - public CharSequence sender; - public String senderAddress; - public String uid; - public boolean read; - public LocalMessage message; - public String uri; - - - public static MessageInfoHolder create(Context context, LocalMessage message, - Account account) { - Contacts contactHelper = K9.isShowContactName() ? Contacts.getInstance(context) : null; - - MessageInfoHolder target = new MessageInfoHolder(); - - target.message = message; - target.compareDate = message.getSentDate(); - if (target.compareDate == null) { - target.compareDate = message.getInternalDate(); - } - - target.read = message.isSet(Flag.SEEN); - - Address[] addrs = message.getFrom(); - - String counterParty; - if (addrs.length > 0 && account.isAnIdentity(addrs[0])) { - CharSequence to = MessageHelper.toFriendly(message.getRecipients(RecipientType.TO), contactHelper); - counterParty = to.toString(); - target.sender = new SpannableStringBuilder(context.getString(R.string.message_to_label)).append(to); - } else { - target.sender = MessageHelper.toFriendly(addrs, contactHelper); - counterParty = target.sender.toString(); - } - - if (addrs.length > 0) { - target.senderAddress = addrs[0].getAddress(); - } else { - // a reasonable fallback "whomever we were corresponding with - target.senderAddress = counterParty; - } - - target.uid = message.getUid(); - target.uri = message.getUri(); - - return target; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof MessageInfoHolder)) { - return false; - } - MessageInfoHolder other = (MessageInfoHolder)o; - return message.equals(other.message); - } - - @Override - public int hashCode() { - return uid.hashCode(); - } -} diff --git a/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java b/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java deleted file mode 100644 index 759744d9d16e4d2732e1727b54555c742049e571..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java +++ /dev/null @@ -1,1046 +0,0 @@ -package com.fsck.k9.external; - - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.Semaphore; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import android.annotation.TargetApi; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.CharArrayBuffer; -import android.database.ContentObserver; -import android.database.CrossProcessCursor; -import android.database.Cursor; -import android.database.CursorWindow; -import android.database.DataSetObserver; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.provider.BaseColumns; - -import com.fsck.k9.Account; -import com.fsck.k9.BuildConfig; -import com.fsck.k9.DI; -import com.fsck.k9.Preferences; -import com.fsck.k9.controller.MessageReference; -import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.SimpleMessagingListener; -import com.fsck.k9.mail.Flag; -import com.fsck.k9.mail.Message; -import com.fsck.k9.mailstore.LocalMessage; -import com.fsck.k9.search.SearchAccount; -import timber.log.Timber; - - -public class MessageProvider extends ContentProvider { - public static String AUTHORITY = BuildConfig.APPLICATION_ID + ".messageprovider"; - public static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); - - private static final String[] DEFAULT_MESSAGE_PROJECTION = new String[] { - MessageColumns._ID, - MessageColumns.SEND_DATE, - MessageColumns.SENDER, - MessageColumns.SUBJECT, - MessageColumns.PREVIEW, - MessageColumns.ACCOUNT, - MessageColumns.URI, - MessageColumns.DELETE_URI, - MessageColumns.SENDER_ADDRESS - }; - private static final String[] DEFAULT_ACCOUNT_PROJECTION = new String[] { - AccountColumns.ACCOUNT_NUMBER, - AccountColumns.ACCOUNT_NAME, - }; - private static final String[] UNREAD_PROJECTION = new String[] { - UnreadColumns.ACCOUNT_NAME, - UnreadColumns.UNREAD - }; - - - private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - private List queryHandlers = new ArrayList<>(); - - /** - * How many simultaneous cursors we can afford to expose at once - */ - Semaphore semaphore = new Semaphore(1); - - ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1); - - - @Override - public boolean onCreate() { - registerQueryHandler(new ThrottlingQueryHandler(new AccountsQueryHandler())); - registerQueryHandler(new ThrottlingQueryHandler(new MessagesQueryHandler())); - registerQueryHandler(new ThrottlingQueryHandler(new UnreadQueryHandler())); - - return true; - } - - public static void init() { - Timber.v("Registering content resolver notifier"); - - final Context context = DI.get(Context.class); - MessagingController messagingController = DI.get(MessagingController.class); - messagingController.addListener(new SimpleMessagingListener() { - @Override - public void folderStatusChanged(Account account, long folderId) { - context.getContentResolver().notifyChange(CONTENT_URI, null); - } - }); - } - - @Override - public String getType(Uri uri) { - Timber.v("MessageProvider/getType: %s", uri); - - return null; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - Timber.v("MessageProvider/query: %s", uri); - - int code = uriMatcher.match(uri); - if (code == -1) { - throw new IllegalStateException("Unrecognized URI: " + uri); - } - - Cursor cursor; - try { - QueryHandler handler = queryHandlers.get(code); - cursor = handler.query(uri, projection, selection, selectionArgs, sortOrder); - } catch (Exception e) { - Timber.e(e, "Unable to execute query for URI: %s", uri); - return null; - } - - return cursor; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - - Timber.v("MessageProvider/delete: %s", uri); - - // Note: can only delete a message - - List segments = uri.getPathSegments(); - int accountId = Integer.parseInt(segments.get(1)); - long folderId = Long.parseLong(segments.get(2)); - String msgUid = segments.get(3); - - // get account - Account myAccount = null; - for (Account account : Preferences.getPreferences().getAccounts()) { - if (account.getAccountNumber() == accountId) { - myAccount = account; - } - } - - if (myAccount == null) { - Timber.e("Could not find account with id %d", accountId); - } - - if (myAccount != null) { - MessageReference messageReference = new MessageReference(myAccount.getUuid(), folderId, msgUid); - MessagingController controller = MessagingController.getInstance(getContext()); - controller.deleteMessage(messageReference); - } - - // FIXME return the actual number of deleted messages - return 0; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - Timber.v("MessageProvider/insert: %s", uri); - - return null; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - Timber.v("MessageProvider/update: %s", uri); - - // TBD - - return 0; - } - - /** - * Register a {@link QueryHandler} to handle a certain {@link Uri} for - * {@link #query(Uri, String[], String, String[], String)} - */ - protected void registerQueryHandler(QueryHandler handler) { - if (queryHandlers.contains(handler)) { - return; - } - queryHandlers.add(handler); - - int code = queryHandlers.indexOf(handler); - uriMatcher.addURI(AUTHORITY, handler.getPath(), code); - } - - - public static class ReverseDateComparator implements Comparator { - @Override - public int compare(MessageInfoHolder object2, MessageInfoHolder object1) { - if (object1.compareDate == null) { - return (object2.compareDate == null ? 0 : 1); - } else if (object2.compareDate == null) { - return -1; - } else { - return object1.compareDate.compareTo(object2.compareDate); - } - } - } - - public interface MessageColumns extends BaseColumns { - /** - * The number of milliseconds since Jan. 1, 1970, midnight GMT. - * - *

Type: INTEGER (long)

- */ - String SEND_DATE = "date"; - - /** - *

Type: TEXT

- */ - String SENDER = "sender"; - - /** - *

Type: TEXT

- */ - String SENDER_ADDRESS = "senderAddress"; - - /** - *

Type: TEXT

- */ - String SUBJECT = "subject"; - - /** - *

Type: TEXT

- */ - String PREVIEW = "preview"; - - /** - *

Type: BOOLEAN

- */ - String UNREAD = "unread"; - - /** - *

Type: TEXT

- */ - String ACCOUNT = "account"; - - /** - *

Type: INTEGER

- */ - String ACCOUNT_NUMBER = "accountNumber"; - - /** - *

Type: BOOLEAN

- */ - String HAS_ATTACHMENTS = "hasAttachments"; - - /** - *

Type: BOOLEAN

- */ - String HAS_STAR = "hasStar"; - - /** - *

Type: INTEGER

- */ - String ACCOUNT_COLOR = "accountColor"; - - String URI = "uri"; - String DELETE_URI = "delUri"; - - /** - * @deprecated the field value is misnamed/misleading - present for compatibility purpose only. To be removed. - */ - @Deprecated - String INCREMENT = "id"; - } - - public interface AccountColumns { - /** - *

Type: INTEGER

- */ - String ACCOUNT_NUMBER = "accountNumber"; - /** - *

Type: String

- */ - String ACCOUNT_NAME = "accountName"; - - - String ACCOUNT_UUID = "accountUuid"; - String ACCOUNT_COLOR = "accountColor"; - } - - public interface UnreadColumns { - /** - *

Type: String

- */ - String ACCOUNT_NAME = "accountName"; - /** - *

Type: INTEGER

- */ - String UNREAD = "unread"; - } - - protected interface QueryHandler { - /** - * The path this instance is able to respond to. - */ - String getPath(); - - Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) - throws Exception; - } - - /** - * Extracts a value from an object. - */ - public interface FieldExtractor { - K getField(T source); - } - - /** - * Extracts the {@link LocalMessage#getDatabaseId() ID} from the given {@link MessageInfoHolder}. The underlying - * {@link Message} is expected to be a {@link LocalMessage}. - */ - public static class IdExtractor implements FieldExtractor { - @Override - public Long getField(MessageInfoHolder source) { - return source.message.getDatabaseId(); - } - } - - public static class CountExtractor implements FieldExtractor { - private Integer count; - - public CountExtractor(int count) { - this.count = count; - } - - @Override - public Integer getField(T source) { - return count; - } - } - - public static class SubjectExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - return source.message.getSubject(); - } - } - - public static class SendDateExtractor implements FieldExtractor { - @Override - public Long getField(MessageInfoHolder source) { - return source.message.getSentDate().getTime(); - } - } - - public static class PreviewExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - return source.message.getPreview(); - } - } - - public static class UriExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - return source.uri; - } - } - - public static class DeleteUriExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - LocalMessage message = source.message; - int accountNumber = message.getAccount().getAccountNumber(); - return CONTENT_URI.buildUpon() - .appendPath("delete_message") - .appendPath(Integer.toString(accountNumber)) - .appendPath(Long.toString(message.getFolder().getDatabaseId())) - .appendPath(message.getUid()) - .build() - .toString(); - } - } - - public static class SenderExtractor implements FieldExtractor { - @Override - public CharSequence getField(MessageInfoHolder source) { - return source.sender; - } - } - - public static class SenderAddressExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - return source.senderAddress; - } - } - - public static class AccountExtractor implements FieldExtractor { - @Override - public String getField(MessageInfoHolder source) { - return source.message.getAccount().getDisplayName(); - } - } - - public static class AccountColorExtractor implements FieldExtractor { - @Override - public Integer getField(MessageInfoHolder source) { - return source.message.getAccount().getChipColor(); - } - } - - public static class AccountNumberExtractor implements FieldExtractor { - @Override - public Integer getField(MessageInfoHolder source) { - return source.message.getAccount().getAccountNumber(); - } - } - - public static class HasAttachmentsExtractor implements FieldExtractor { - @Override - public Boolean getField(MessageInfoHolder source) { - return source.message.hasAttachments(); - } - } - - public static class HasStarExtractor implements FieldExtractor { - @Override - public Boolean getField(MessageInfoHolder source) { - return source.message.isSet(Flag.FLAGGED); - } - } - - public static class UnreadExtractor implements FieldExtractor { - @Override - public Boolean getField(MessageInfoHolder source) { - return !source.read; - } - } - - /** - * @deprecated having an incremental value has no real interest, implemented for compatibility only - */ - @Deprecated - public static class IncrementExtractor implements FieldExtractor { - private int count = 0; - - - @Override - public Integer getField(MessageInfoHolder source) { - return count++; - } - } - - /** - * Retrieve messages from the integrated inbox. - */ - protected class MessagesQueryHandler implements QueryHandler { - - @Override - public String getPath() { - return "inbox_messages/"; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) - throws Exception { - return getMessages(projection); - } - - protected MatrixCursor getMessages(String[] projection) throws InterruptedException { - // new code for integrated inbox, only execute this once as it will be processed afterwards via the listener - SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(); - MessagingController msgController = MessagingController.getInstance(getContext()); - - List messages = msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch()); - List holders = convertToMessageInfoHolder(messages); - - // TODO add sort order parameter - Collections.sort(holders, new ReverseDateComparator()); - - String[] projectionToUse; - if (projection == null) { - projectionToUse = DEFAULT_MESSAGE_PROJECTION; - } else { - projectionToUse = projection; - } - - LinkedHashMap> extractors = - resolveMessageExtractors(projectionToUse, holders.size()); - int fieldCount = extractors.size(); - - String[] actualProjection = extractors.keySet().toArray(new String[fieldCount]); - MatrixCursor cursor = new MatrixCursor(actualProjection); - - for (MessageInfoHolder holder : holders) { - Object[] o = new Object[fieldCount]; - - int i = 0; - for (FieldExtractor extractor : extractors.values()) { - o[i] = extractor.getField(holder); - i += 1; - } - - cursor.addRow(o); - } - - return cursor; - } - - private List convertToMessageInfoHolder(List messages) { - List holders = new ArrayList<>(); - - Context context = getContext(); - for (LocalMessage message : messages) { - Account messageAccount = message.getAccount(); - MessageInfoHolder messageInfoHolder = MessageInfoHolder.create(context, message, messageAccount); - - holders.add(messageInfoHolder); - } - - return holders; - } - - protected LinkedHashMap> resolveMessageExtractors( - String[] projection, int count) { - LinkedHashMap> extractors = new LinkedHashMap<>(); - - for (String field : projection) { - if (extractors.containsKey(field)) { - continue; - } - if (MessageColumns._ID.equals(field)) { - extractors.put(field, new IdExtractor()); - } else if (MessageColumns._COUNT.equals(field)) { - extractors.put(field, new CountExtractor<>(count)); - } else if (MessageColumns.SUBJECT.equals(field)) { - extractors.put(field, new SubjectExtractor()); - } else if (MessageColumns.SENDER.equals(field)) { - extractors.put(field, new SenderExtractor()); - } else if (MessageColumns.SENDER_ADDRESS.equals(field)) { - extractors.put(field, new SenderAddressExtractor()); - } else if (MessageColumns.SEND_DATE.equals(field)) { - extractors.put(field, new SendDateExtractor()); - } else if (MessageColumns.PREVIEW.equals(field)) { - extractors.put(field, new PreviewExtractor()); - } else if (MessageColumns.URI.equals(field)) { - extractors.put(field, new UriExtractor()); - } else if (MessageColumns.DELETE_URI.equals(field)) { - extractors.put(field, new DeleteUriExtractor()); - } else if (MessageColumns.UNREAD.equals(field)) { - extractors.put(field, new UnreadExtractor()); - } else if (MessageColumns.ACCOUNT.equals(field)) { - extractors.put(field, new AccountExtractor()); - } else if (MessageColumns.ACCOUNT_COLOR.equals(field)) { - extractors.put(field, new AccountColorExtractor()); - } else if (MessageColumns.ACCOUNT_NUMBER.equals(field)) { - extractors.put(field, new AccountNumberExtractor()); - } else if (MessageColumns.HAS_ATTACHMENTS.equals(field)) { - extractors.put(field, new HasAttachmentsExtractor()); - } else if (MessageColumns.HAS_STAR.equals(field)) { - extractors.put(field, new HasStarExtractor()); - } else if (MessageColumns.INCREMENT.equals(field)) { - extractors.put(field, new IncrementExtractor()); - } - } - return extractors; - } - } - - /** - * Retrieve the account list. - */ - protected class AccountsQueryHandler implements QueryHandler { - - - @Override - public String getPath() { - return "accounts"; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) - throws Exception { - return getAllAccounts(projection); - } - - public Cursor getAllAccounts(String[] projection) { - if (projection == null) { - projection = DEFAULT_ACCOUNT_PROJECTION; - } - - MatrixCursor cursor = new MatrixCursor(projection); - - for (Account account : Preferences.getPreferences().getAccounts()) { - Object[] values = new Object[projection.length]; - - int fieldIndex = 0; - for (String field : projection) { - if (AccountColumns.ACCOUNT_NUMBER.equals(field)) { - values[fieldIndex] = account.getAccountNumber(); - } else if (AccountColumns.ACCOUNT_NAME.equals(field)) { - values[fieldIndex] = account.getDisplayName(); - } else if (AccountColumns.ACCOUNT_UUID.equals(field)) { - values[fieldIndex] = account.getUuid(); - } else if (AccountColumns.ACCOUNT_COLOR.equals(field)) { - values[fieldIndex] = account.getChipColor(); - } else { - values[fieldIndex] = null; - } - ++fieldIndex; - } - - cursor.addRow(values); - } - - return cursor; - } - } - - /** - * Retrieve the unread message count for a given account specified by its {@link Account#getAccountNumber() number}. - */ - protected class UnreadQueryHandler implements QueryHandler { - - @Override - public String getPath() { - return "account_unread/#"; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) - throws Exception { - List segments = uri.getPathSegments(); - int accountId = Integer.parseInt(segments.get(1)); - - /* - * This method below calls Account.getStats() which uses EmailProvider to do its work. - * For this to work we need to clear the calling identity. Otherwise accessing - * EmailProvider will fail because it's not exported so third-party apps can't access it - * directly. - */ - long identityToken = Binder.clearCallingIdentity(); - try { - return getAccountUnread(accountId); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - private Cursor getAccountUnread(int accountNumber) { - - MatrixCursor cursor = new MatrixCursor(UNREAD_PROJECTION); - - Account myAccount; - - Object[] values = new Object[2]; - - Context context = getContext(); - MessagingController controller = MessagingController.getInstance(context); - Collection accounts = Preferences.getPreferences().getAccounts(); - - for (Account account : accounts) { - if (account.getAccountNumber() == accountNumber) { - myAccount = account; - values[0] = myAccount.getDisplayName(); - values[1] = controller.getUnreadMessageCount(account); - cursor.addRow(values); - } - } - - return cursor; - } - } - - /** - * Cursor wrapper that release a semaphore on close. Close is also triggered on {@link #finalize()}. - */ - protected static class MonitoredCursor implements CrossProcessCursor { - /** - * The underlying cursor implementation that handles regular requests - */ - private CrossProcessCursor cursor; - - /** - * Whether {@link #close()} was invoked - */ - private AtomicBoolean closed = new AtomicBoolean(false); - - private Semaphore semaphore; - - - protected MonitoredCursor(CrossProcessCursor cursor, Semaphore semaphore) { - this.cursor = cursor; - this.semaphore = semaphore; - } - - @Override - public void close() { - if (closed.compareAndSet(false, true)) { - cursor.close(); - Timber.d("Cursor closed, null'ing & releasing semaphore"); - cursor = null; - semaphore.release(); - } - } - - @Override - public boolean isClosed() { - return closed.get() || cursor.isClosed(); - } - - @Override - protected void finalize() throws Throwable { - close(); - super.finalize(); - } - - protected void checkClosed() throws IllegalStateException { - if (closed.get()) { - throw new IllegalStateException("Cursor was closed"); - } - } - - @Override - public void fillWindow(int pos, CursorWindow winow) { - checkClosed(); - cursor.fillWindow(pos, winow); - } - - @Override - public CursorWindow getWindow() { - checkClosed(); - return cursor.getWindow(); - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - checkClosed(); - return cursor.onMove(oldPosition, newPosition); - } - - @Override - public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { - checkClosed(); - cursor.copyStringToBuffer(columnIndex, buffer); - } - - @Override - public void deactivate() { - checkClosed(); - cursor.deactivate(); - } - - @Override - public byte[] getBlob(int columnIndex) { - checkClosed(); - return cursor.getBlob(columnIndex); - } - - @Override - public int getColumnCount() { - checkClosed(); - return cursor.getColumnCount(); - } - - @Override - public int getColumnIndex(String columnName) { - checkClosed(); - return cursor.getColumnIndex(columnName); - } - - @Override - public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { - checkClosed(); - return cursor.getColumnIndexOrThrow(columnName); - } - - @Override - public String getColumnName(int columnIndex) { - checkClosed(); - return cursor.getColumnName(columnIndex); - } - - @Override - public String[] getColumnNames() { - checkClosed(); - return cursor.getColumnNames(); - } - - @Override - public int getCount() { - checkClosed(); - return cursor.getCount(); - } - - @Override - public double getDouble(int columnIndex) { - checkClosed(); - return cursor.getDouble(columnIndex); - } - - @Override - public Bundle getExtras() { - checkClosed(); - return cursor.getExtras(); - } - - @Override - public float getFloat(int columnIndex) { - checkClosed(); - return cursor.getFloat(columnIndex); - } - - @Override - public int getInt(int columnIndex) { - checkClosed(); - return cursor.getInt(columnIndex); - } - - @Override - public long getLong(int columnIndex) { - checkClosed(); - return cursor.getLong(columnIndex); - } - - @Override - public int getPosition() { - checkClosed(); - return cursor.getPosition(); - } - - @Override - public short getShort(int columnIndex) { - checkClosed(); - return cursor.getShort(columnIndex); - } - - @Override - public String getString(int columnIndex) { - checkClosed(); - return cursor.getString(columnIndex); - } - - @Override - public boolean getWantsAllOnMoveCalls() { - checkClosed(); - return cursor.getWantsAllOnMoveCalls(); - } - - @TargetApi(Build.VERSION_CODES.M) - @Override - public void setExtras(Bundle extras) { - cursor.setExtras(extras); - } - - @Override - public boolean isAfterLast() { - checkClosed(); - return cursor.isAfterLast(); - } - - @Override - public boolean isBeforeFirst() { - checkClosed(); - return cursor.isBeforeFirst(); - } - - @Override - public boolean isFirst() { - checkClosed(); - return cursor.isFirst(); - } - - @Override - public boolean isLast() { - checkClosed(); - return cursor.isLast(); - } - - @Override - public boolean isNull(int columnIndex) { - checkClosed(); - return cursor.isNull(columnIndex); - } - - @Override - public boolean move(int offset) { - checkClosed(); - return cursor.move(offset); - } - - @Override - public boolean moveToFirst() { - checkClosed(); - return cursor.moveToFirst(); - } - - @Override - public boolean moveToLast() { - checkClosed(); - return cursor.moveToLast(); - } - - @Override - public boolean moveToNext() { - checkClosed(); - return cursor.moveToNext(); - } - - @Override - public boolean moveToPosition(int position) { - checkClosed(); - return cursor.moveToPosition(position); - } - - @Override - public boolean moveToPrevious() { - checkClosed(); - return cursor.moveToPrevious(); - } - - @Override - public void registerContentObserver(ContentObserver observer) { - checkClosed(); - cursor.registerContentObserver(observer); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - checkClosed(); - cursor.registerDataSetObserver(observer); - } - - @SuppressWarnings("deprecation") - @Override - public boolean requery() { - checkClosed(); - return cursor.requery(); - } - - @Override - public Bundle respond(Bundle extras) { - checkClosed(); - return cursor.respond(extras); - } - - @Override - public void setNotificationUri(ContentResolver cr, Uri uri) { - checkClosed(); - cursor.setNotificationUri(cr, uri); - } - - @Override - public void unregisterContentObserver(ContentObserver observer) { - checkClosed(); - cursor.unregisterContentObserver(observer); - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - checkClosed(); - cursor.unregisterDataSetObserver(observer); - } - - @Override - public int getType(int columnIndex) { - checkClosed(); - return cursor.getType(columnIndex); - } - - @Override - public Uri getNotificationUri() { - return null; - } - } - - protected class ThrottlingQueryHandler implements QueryHandler { - private QueryHandler delegate; - - - public ThrottlingQueryHandler(QueryHandler delegate) { - this.delegate = delegate; - } - - @Override - public String getPath() { - return delegate.getPath(); - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) - throws Exception { - semaphore.acquire(); - - Cursor cursor = null; - try { - cursor = delegate.query(uri, projection, selection, selectionArgs, sortOrder); - } finally { - if (cursor == null) { - semaphore.release(); - } - } - - // Android content resolvers can only process CrossProcessCursor instances - if (!(cursor instanceof CrossProcessCursor)) { - Timber.w("Unsupported cursor, returning null: %s", cursor); - semaphore.release(); - return null; - } - - MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor, semaphore); - - // Use a weak reference not to actively prevent garbage collection - final WeakReference weakReference = new WeakReference<>(wrapped); - - // Make sure the cursor is closed after 30 seconds - scheduledPool.schedule(new Runnable() { - - @Override - public void run() { - MonitoredCursor monitored = weakReference.get(); - if (monitored != null && !monitored.isClosed()) { - Timber.w("Forcibly closing remotely exposed cursor"); - try { - monitored.close(); - } catch (Exception e) { - Timber.w(e, "Exception while forcibly closing cursor"); - } - } - } - }, 30, TimeUnit.SECONDS); - - return wrapped; - } - } -} diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/KoinModule.kt b/app/k9mail/src/main/java/com/fsck/k9/widget/list/KoinModule.kt index 05ddb774ac58b2cf6b28e80b500b7bc00d2c0a56..c7cebde8fe459dbb298222696a95f5ccf7696500 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/list/KoinModule.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/widget/list/KoinModule.kt @@ -1,7 +1,8 @@ package com.fsck.k9.widget.list +import app.k9mail.ui.widget.list.MessageListWidgetConfig import org.koin.dsl.module -val messageListWidgetModule = module { - single { MessageListWidgetUpdateListener(get()) } +val messageListWidgetConfigModule = module { + single { K9MessageListWidgetConfig() } } diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListRemoteViewFactory.java b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListRemoteViewFactory.java deleted file mode 100644 index 5ebc4115747af3d49390dcaeeaf90bc7967f5697..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListRemoteViewFactory.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.fsck.k9.widget.list; - - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Locale; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Binder; -import androidx.core.content.ContextCompat; -import android.text.SpannableString; -import android.text.style.StyleSpan; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.RemoteViewsService; - -import com.fsck.k9.K9; -import com.fsck.k9.R; -import com.fsck.k9.external.MessageProvider; - - -public class MessageListRemoteViewFactory implements RemoteViewsService.RemoteViewsFactory { - private static final String[] MAIL_LIST_PROJECTIONS = { - MessageProvider.MessageColumns.SENDER, - MessageProvider.MessageColumns.SEND_DATE, - MessageProvider.MessageColumns.SUBJECT, - MessageProvider.MessageColumns.PREVIEW, - MessageProvider.MessageColumns.UNREAD, - MessageProvider.MessageColumns.HAS_ATTACHMENTS, - MessageProvider.MessageColumns.URI, - MessageProvider.MessageColumns.ACCOUNT_COLOR, - }; - - - private final Context context; - private final Calendar calendar; - private final ArrayList mailItems = new ArrayList<>(25); - private boolean senderAboveSubject; - private int readTextColor; - private int unreadTextColor; - - - public MessageListRemoteViewFactory(Context context) { - this.context = context; - calendar = Calendar.getInstance(); - } - - @Override - public void onCreate() { - senderAboveSubject = K9.isMessageListSenderAboveSubject(); - readTextColor = ContextCompat.getColor(context, R.color.message_list_widget_text_read); - unreadTextColor = ContextCompat.getColor(context, R.color.message_list_widget_text_unread); - } - - @Override - public void onDataSetChanged() { - long identityToken = Binder.clearCallingIdentity(); - try { - loadMessageList(); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - private void loadMessageList() { - mailItems.clear(); - - Uri unifiedInboxUri = MessageProvider.CONTENT_URI.buildUpon().appendPath("inbox_messages").build(); - Cursor cursor = context.getContentResolver().query(unifiedInboxUri, MAIL_LIST_PROJECTIONS, null, null, null); - - if (cursor == null) { - return; - } - - try { - while (cursor.moveToNext()) { - String sender = cursor.getString(0); - long date = cursor.isNull(1) ? 0L : cursor.getLong(1); - String subject = cursor.getString(2); - String preview = cursor.getString(3); - boolean unread = toBoolean(cursor.getString(4)); - boolean hasAttachment = toBoolean(cursor.getString(5)); - Uri viewUri = Uri.parse(cursor.getString(6)); - int color = cursor.getInt(7); - - mailItems.add(new MailItem(sender, date, subject, preview, unread, hasAttachment, viewUri, color)); - } - } finally { - cursor.close(); - } - } - - @Override - public void onDestroy() { - // Implement interface - } - - @Override - public int getCount() { - return mailItems.size(); - } - - @Override - public RemoteViews getViewAt(int position) { - RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.message_list_widget_list_item); - MailItem item = mailItems.get(position); - - CharSequence sender = item.unread ? bold(item.sender) : item.sender; - CharSequence subject = item.unread ? bold(item.subject) : item.subject; - - if (senderAboveSubject) { - remoteView.setTextViewText(R.id.sender, sender); - remoteView.setTextViewText(R.id.mail_subject, subject); - } else { - remoteView.setTextViewText(R.id.sender, subject); - remoteView.setTextViewText(R.id.mail_subject, sender); - } - remoteView.setTextViewText(R.id.mail_date, item.getDateFormatted("%d %s")); - remoteView.setTextViewText(R.id.mail_preview, item.preview); - - int textColor = item.getTextColor(); - remoteView.setTextColor(R.id.sender, textColor); - remoteView.setTextColor(R.id.mail_subject, textColor); - remoteView.setTextColor(R.id.mail_date, textColor); - remoteView.setTextColor(R.id.mail_preview, textColor); - - if (item.hasAttachment) { - remoteView.setInt(R.id.attachment, "setVisibility", View.VISIBLE); - } else { - remoteView.setInt(R.id.attachment, "setVisibility", View.GONE); - } - - Intent intent = new Intent(); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.setData(item.uri); - remoteView.setOnClickFillInIntent(R.id.mail_list_item, intent); - - remoteView.setInt(R.id.chip, "setBackgroundColor", item.color); - - return remoteView; - } - - @Override - public RemoteViews getLoadingView() { - RemoteViews loadingView = new RemoteViews(context.getPackageName(), R.layout.message_list_widget_loading); - loadingView.setTextViewText(R.id.loadingText, context.getString(com.fsck.k9.ui.R.string.mail_list_widget_loading)); - loadingView.setViewVisibility(R.id.loadingText, View.VISIBLE); - return loadingView; - } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - private CharSequence bold(String text) { - SpannableString spannableString = new SpannableString(text); - spannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), 0); - return spannableString; - } - - private boolean toBoolean(String value) { - return Boolean.valueOf(value); - } - - - private class MailItem { - final long date; - final String sender; - final String preview; - final String subject; - final boolean unread; - final boolean hasAttachment; - final Uri uri; - final int color; - - - MailItem(String sender, long date, String subject, String preview, boolean unread, boolean hasAttachment, - Uri viewUri, int color) { - this.sender = sender; - this.date = date; - this.preview = preview; - this.subject = subject; - this.unread = unread; - this.uri = viewUri; - this.hasAttachment = hasAttachment; - this.color = color; - } - - int getTextColor() { - return unread ? unreadTextColor : readTextColor; - } - - String getDateFormatted(String format) { - calendar.setTimeInMillis(date); - - return String.format(format, - calendar.get(Calendar.DAY_OF_MONTH), - calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())); - } - } -} - diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.java b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.java deleted file mode 100644 index c7a9ac00734e7ccd8a90150ae838f923f5b25c5a..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.fsck.k9.widget.list; - - -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.widget.RemoteViews; - -import com.fsck.k9.R; -import com.fsck.k9.activity.MessageCompose; -import com.fsck.k9.activity.MessageList; -import com.fsck.k9.search.SearchAccount; - -import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; -import static com.fsck.k9.helper.PendingIntentCompat.FLAG_IMMUTABLE; -import static com.fsck.k9.helper.PendingIntentCompat.FLAG_MUTABLE; - - -public class MessageListWidgetProvider extends AppWidgetProvider { - private static final String ACTION_UPDATE_MESSAGE_LIST = "UPDATE_MESSAGE_LIST"; - - - public static void triggerMessageListWidgetUpdate(Context context) { - Context appContext = context.getApplicationContext(); - AppWidgetManager widgetManager = AppWidgetManager.getInstance(appContext); - ComponentName widget = new ComponentName(appContext, MessageListWidgetProvider.class); - int[] widgetIds = widgetManager.getAppWidgetIds(widget); - - Intent intent = new Intent(context, MessageListWidgetProvider.class); - intent.setAction(ACTION_UPDATE_MESSAGE_LIST); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds); - context.sendBroadcast(intent); - } - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - for (int appWidgetId : appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId); - } - } - - private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { - RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.message_list_widget_layout); - - views.setTextViewText(R.id.folder, context.getString(com.fsck.k9.ui.R.string.integrated_inbox_title)); - - Intent intent = new Intent(context, MessageListWidgetService.class); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); - views.setRemoteAdapter(R.id.listView, intent); - - PendingIntent viewAction = viewActionTemplatePendingIntent(context); - views.setPendingIntentTemplate(R.id.listView, viewAction); - - PendingIntent composeAction = composeActionPendingIntent(context); - views.setOnClickPendingIntent(R.id.new_message, composeAction); - - PendingIntent headerClickAction = viewUnifiedInboxPendingIntent(context); - views.setOnClickPendingIntent(R.id.top_controls, headerClickAction); - - appWidgetManager.updateAppWidget(appWidgetId, views); - } - - @Override - public void onReceive(Context context, Intent intent) { - super.onReceive(context, intent); - - String action = intent.getAction(); - if (action.equals(ACTION_UPDATE_MESSAGE_LIST)) { - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listView); - } - } - - private PendingIntent viewActionTemplatePendingIntent(Context context) { - Intent intent = new Intent(context, MessageList.class); - intent.setAction(Intent.ACTION_VIEW); - - return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT | FLAG_MUTABLE); - } - - private PendingIntent viewUnifiedInboxPendingIntent(Context context) { - SearchAccount unifiedInboxAccount = SearchAccount.createUnifiedInboxAccount(); - Intent intent = MessageList.intentDisplaySearch( - context, unifiedInboxAccount.getRelatedSearch(), true, true, true); - - return PendingIntent.getActivity(context, -1, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); - } - - private PendingIntent composeActionPendingIntent(Context context) { - Intent intent = new Intent(context, MessageCompose.class); - intent.setAction(MessageCompose.ACTION_COMPOSE); - - return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); - } -} diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.kt b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..805be2eb139cdd1bafb3a1791644498f744f9ffc --- /dev/null +++ b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetProvider.kt @@ -0,0 +1,9 @@ +package com.fsck.k9.widget.list + +import app.k9mail.ui.widget.list.MessageListWidgetConfig + +class MessageListWidgetProvider : app.k9mail.ui.widget.list.MessageListWidgetProvider() + +internal class K9MessageListWidgetConfig : MessageListWidgetConfig { + override val providerClass = MessageListWidgetProvider::class.java +} diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetService.java b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetService.java deleted file mode 100644 index 980a585c620c27ff614b1aa34084bbedcbcf6bbb..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fsck.k9.widget.list; - - -import android.content.Intent; -import android.widget.RemoteViewsService; - - -public class MessageListWidgetService extends RemoteViewsService { - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) { - return new MessageListRemoteViewFactory(getApplicationContext()); - } -} diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetUpdateListener.kt b/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetUpdateListener.kt deleted file mode 100644 index 4a84ff2750db67aeadf96b3b43a0a9d6d6ded851..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/list/MessageListWidgetUpdateListener.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.fsck.k9.widget.list - -import android.content.Context -import com.fsck.k9.Account -import com.fsck.k9.controller.SimpleMessagingListener -import com.fsck.k9.core.BuildConfig -import com.fsck.k9.mail.Message -import timber.log.Timber - -class MessageListWidgetUpdateListener(private val context: Context) : SimpleMessagingListener() { - - private fun updateMailListWidget() { - try { - MessageListWidgetProvider.triggerMessageListWidgetUpdate(context) - } catch (e: RuntimeException) { - if (BuildConfig.DEBUG) { - throw e - } else { - Timber.e(e, "Error while updating message list widget") - } - } - } - - override fun synchronizeMailboxRemovedMessage(account: Account, folderServerId: String, messageServerId: String) { - updateMailListWidget() - } - - override fun synchronizeMailboxNewMessage(account: Account, folderServerId: String, message: Message) { - updateMailListWidget() - } - - override fun folderStatusChanged(account: Account, folderId: Long) { - updateMailListWidget() - } -} diff --git a/app/k9mail/src/main/res/layout/message_list_widget_loading.xml b/app/k9mail/src/main/res/layout/message_list_widget_loading.xml deleted file mode 100644 index 2df87f53401c927278deee81478daf2d0caf8f24..0000000000000000000000000000000000000000 --- a/app/k9mail/src/main/res/layout/message_list_widget_loading.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/app/k9mail/src/main/res/values/themes.xml b/app/k9mail/src/main/res/values/themes.xml index 9982756f2c75259e2f0ff5f0d0de7edda0cdf5d3..6da76fe4a3f45b312daf0d24e88068e5a36f29bb 100644 --- a/app/k9mail/src/main/res/values/themes.xml +++ b/app/k9mail/src/main/res/values/themes.xml @@ -121,6 +121,7 @@ @drawable/ic_person_plus @color/color_default_foreground #ffababab + @array/contact_picture_fallback_background_colors_light @color/color_contact_token_background @android:color/background_light #77aa22 @@ -148,6 +149,26 @@ @style/Widget.MaterialDrawerStyle.K9.Light @style/Widget.MaterialDrawerHeaderStyle.K9.Light @color/color_default_foreground + + #ffffff + @drawable/ic_check_circle + ?attr/iconActionMarkAsRead + ?attr/iconActionMarkAsUnread + ?attr/iconActionFlag + ?attr/iconActionUnflag + @color/material_orange_600 + ?attr/iconActionArchive + @color/material_green_600 + ?attr/iconActionDelete + @color/material_red_600 + ?attr/iconActionSpam + @color/material_red_700 + ?attr/iconActionMove + @color/material_purple_500 + + @color/color_default_background + @color/material_blue_600 + @color/material_blue_600