Remove JMAP app

The JMAP backend is still part of the repository. Only currently unused.
This commit is contained in:
cketti 2022-01-20 00:28:01 +01:00
parent fda4e8243a
commit 262f97812f
35 changed files with 0 additions and 1950 deletions

View file

@ -1,135 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
if (rootProject.testCoverage) {
apply plugin: 'jacoco'
}
dependencies {
implementation project(":app:ui:legacy")
implementation project(":app:core")
implementation project(":app:storage")
implementation project(":app:crypto-openpgp")
implementation project(":backend:imap")
implementation project(":backend:pop3")
implementation project(":backend:webdav")
implementation project(":backend:jmap")
implementation "androidx.appcompat:appcompat:${versions.androidxAppCompat}"
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "androidx.constraintlayout:constraintlayout:${versions.androidxConstraintLayout}"
implementation "com.google.android.material:material:${versions.materialComponents}"
implementation "androidx.navigation:navigation-fragment-ktx:${versions.androidxNavigation}"
implementation "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}"
implementation "com.github.bumptech.glide:glide:${versions.glide}"
kapt "com.github.bumptech.glide:compiler:${versions.glide}"
// Required for DependencyInjectionTest to be able to resolve OpenPgpApiManager
testImplementation project(':plugins:openpgp-api-lib:openpgp-api')
testImplementation "org.robolectric:robolectric:${versions.robolectric}"
testImplementation "junit:junit:${versions.junit}"
testImplementation "com.google.truth:truth:${versions.truth}"
testImplementation "org.mockito:mockito-core:${versions.mockito}"
testImplementation "org.mockito.kotlin:mockito-kotlin:${versions.mockitoKotlin}"
testImplementation "io.insert-koin:koin-test-junit4:${versions.koin}"
}
android {
compileSdkVersion buildConfig.compileSdk
buildToolsVersion buildConfig.buildTools
defaultConfig {
applicationId "com.fsck.k9.jmap"
testApplicationId "com.fsck.k9.jmap.tests"
versionCode 1
versionName 'JMAP DEV'
minSdkVersion buildConfig.minSdk
targetSdkVersion buildConfig.targetSdk
generatedDensities = ['mdpi', 'hdpi', 'xhdpi']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release
}
buildTypes {
release {
if (project.hasProperty('storeFile')) {
signingConfig signingConfigs.release
}
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "DEVELOPER_MODE", "false"
}
debug {
applicationIdSuffix ".debug"
testCoverageEnabled rootProject.testCoverage
minifyEnabled false
buildConfigField "boolean", "DEVELOPER_MODE", "true"
}
}
// Do not abort build if lint finds errors
lintOptions {
abortOnError false
lintConfig file("$rootProject.projectDir/config/lint/lint.xml")
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/README'
exclude 'LICENSE.txt'
exclude 'META-INF/*.kotlin_module'
}
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
}
kotlinOptions {
jvmTarget = kotlinJvmVersion
}
buildFeatures {
dataBinding = true
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
if (project.hasProperty('keyAlias')) {
android.signingConfigs.release.keyAlias = keyAlias
}
if (project.hasProperty('keyPassword')) {
android.signingConfigs.release.keyPassword = keyPassword
}
if (project.hasProperty('storeFile')) {
android.signingConfigs.release.storeFile = file(storeFile)
}
if (project.hasProperty('storePassword')) {
android.signingConfigs.release.storePassword = storePassword
}

View file

@ -1,52 +0,0 @@
# Add project specific ProGuard rules here.
-dontobfuscate
# Preserve the line number information for debugging stack traces.
-keepattributes SourceFile,LineNumberTable
# Library specific rules
-dontnote android.net.http.*
-dontnote org.apache.commons.codec.**
-dontnote org.apache.http.**
-dontnote com.squareup.moshi.**
-dontnote com.github.amlcurran.showcaseview.**
-dontnote de.cketti.safecontentresolver.**
-dontnote com.tokenautocomplete.**
-keep class rs.ltt.jmap.common.** {*;}
-dontwarn okio.**
-dontwarn com.squareup.moshi.**
# Glide
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public class * extends com.bumptech.glide.module.LibraryGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# Project specific rules
-dontnote com.fsck.k9.ui.messageview.**
-dontnote com.fsck.k9.view.**
-keep public class org.openintents.openpgp.**
-keepclassmembers class * extends androidx.appcompat.widget.SearchView {
public <init>(android.content.Context);
}
# okhttp rules
# see: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform

View file

@ -1,300 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"
package="com.fsck.k9.jmap">
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:name="com.fsck.k9.App"
android:allowTaskReparenting="false"
android:usesCleartextTraffic="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.K9.Startup"
android:resizeableActivity="true"
android:allowBackup="false"
tools:replace="android:theme">
<meta-data
android:name="android.app.default_searchable"
android:value="com.fsck.k9.activity.Search"/>
<!-- TODO: Remove once minSdkVersion has been changed to 24+ -->
<meta-data
android:name="com.lge.support.SPLIT_WINDOW"
android:value="true"/>
<uses-library
android:name="com.sec.android.app.multiwindow"
android:required="false"/>
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true"/>
<meta-data
android:name="com.samsung.android.sdk.multiwindow.penwindow.enable"
android:value="true"/>
<activity
android:name="com.fsck.k9.ui.onboarding.OnboardingActivity"
android:label="@string/welcome_message_title" />
<activity
android:name="com.fsck.k9.ui.settings.account.OpenPgpAppSelectDialog"
android:configChanges="locale"
android:theme="@style/Theme.K9.Dialog.Translucent.DayNight"
/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupBasics"
android:configChanges="locale"
android:label="@string/account_setup_basics_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupAccountType"
android:configChanges="locale"
android:label="@string/account_setup_account_type_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupIncoming"
android:configChanges="locale"
android:label="@string/account_setup_incoming_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupComposition"
android:configChanges="locale"
android:label="@string/account_settings_composition_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupOutgoing"
android:configChanges="locale"
android:label="@string/account_setup_outgoing_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupOptions"
android:configChanges="locale"
android:label="@string/account_setup_options_title"/>
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupNames"
android:configChanges="locale"
android:label="@string/account_setup_names_title"/>
<activity
android:name="com.fsck.k9.activity.ChooseAccount"
android:configChanges="locale"
android:label="@string/choose_account_title"
android:noHistory="true" />
<activity
android:name="com.fsck.k9.ui.choosefolder.ChooseFolderActivity"
android:configChanges="locale"
android:label="@string/choose_folder_title"
android:noHistory="true" />
<activity
android:name="com.fsck.k9.activity.ChooseIdentity"
android:configChanges="locale"
android:label="@string/choose_identity_title" />
<activity
android:name="com.fsck.k9.activity.ManageIdentities"
android:configChanges="locale"
android:label="@string/manage_identities_title"/>
<activity
android:name="com.fsck.k9.activity.EditIdentity"
android:configChanges="locale"
android:label="@string/edit_identity_title"/>
<activity
android:name="com.fsck.k9.ui.notification.DeleteConfirmationActivity"
android:excludeFromRecents="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/Theme.K9.Dialog.Translucent.DayNight"/>
<!-- XXX Note: this activity is hacked to ignore config changes,
since it doesn't currently handle them correctly in code. -->
<activity
android:name="com.fsck.k9.activity.setup.AccountSetupCheckSettings"
android:configChanges="keyboardHidden|orientation|locale"
android:label="@string/account_setup_check_settings_title"/>
<activity
android:name="com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity"
android:configChanges="locale"
android:label="@string/ac_transfer_title"
/>
<activity android:name="com.fsck.k9.activity.MessageList">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
<!-- TODO: Remove once minSdkVersion has been changed to 24+ -->
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.PENWINDOW_LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.fsck.k9.activity.MessageCompose"
android:configChanges="locale"
android:enabled="false"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.SENDTO"/>
<data android:scheme="mailto"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<data android:mimeType="*/*"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<data android:mimeType="*/*"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="mailto"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
<intent-filter>
<action android:name="org.autocrypt.PEER_ACTION"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value="com.fsck.k9.directshare.K9ChooserTargetService" />
</activity>
<!-- Search Activity - searchable -->
<activity
android:name="com.fsck.k9.activity.Search"
android:configChanges="locale"
android:label="@string/search_action"
android:uiOptions="splitActionBarWhenNarrow">
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
<activity
android:name="com.fsck.k9.activity.UpgradeDatabases"
android:label="@string/upgrade_databases_title"/>
<activity
android:name="com.fsck.k9.ui.managefolders.ManageFoldersActivity"
android:label="@string/folders_action" />
<activity
android:name="com.fsck.k9.ui.settings.SettingsActivity"
android:label="@string/prefs_title" />
<activity
android:name="com.fsck.k9.ui.settings.general.GeneralSettingsActivity"
android:label="@string/general_settings_title" />
<activity
android:name="com.fsck.k9.ui.settings.account.AccountSettingsActivity"
android:label="@string/account_settings_title_fmt" />
<activity
android:name="com.fsck.k9.ui.addaccount.AddAccountActivity"
android:label="@string/add_account_action" />
<service
android:name="com.fsck.k9.notification.NotificationActionService"
android:enabled="true"/>
<service
android:name="com.fsck.k9.service.DatabaseUpgradeService"
android:exported="false"/>
<service
android:name="com.fsck.k9.account.AccountRemoverService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
<provider
android:name="com.fsck.k9.provider.AttachmentProvider"
android:authorities="${applicationId}.attachmentprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
android:value="true" />
</provider>
<provider
android:name="com.fsck.k9.provider.RawMessageProvider"
android:authorities="${applicationId}.rawmessageprovider"
android:exported="false">
<meta-data
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
android:value="true" />
</provider>
<provider
android:name="com.fsck.k9.provider.EmailProvider"
android:authorities="${applicationId}.provider.email"
android:exported="false"/>
<provider
android:name="com.fsck.k9.provider.DecryptedFileProvider"
android:authorities="${applicationId}.decryptedfileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/decrypted_file_provider_paths" />
</provider>
<provider
android:name="com.fsck.k9.provider.AttachmentTempFileProvider"
android:authorities="${applicationId}.tempfileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/temp_file_provider_paths" />
</provider>
</application>
</manifest>

View file

@ -1,35 +0,0 @@
package com.fsck.k9
import android.app.Application
import com.fsck.k9.activity.MessageCompose
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.ui.base.ThemeManager
import org.koin.android.ext.android.inject
class App : Application() {
private val messagingController: MessagingController by inject()
private val messagingListenerProvider: MessagingListenerProvider by inject()
private val themeManager: ThemeManager by inject()
override fun onCreate() {
Core.earlyInit()
super.onCreate()
DI.start(this, coreModules + uiModules + appModules)
K9.init(this)
Core.init(this)
themeManager.init()
messagingListenerProvider.listeners.forEach { listener ->
messagingController.addListener(listener)
}
}
companion object {
val appConfig = AppConfig(
componentsToDisable = listOf(MessageCompose::class.java)
)
}
}

View file

@ -1,31 +0,0 @@
package com.fsck.k9
import com.fsck.k9.backends.backendsModule
import com.fsck.k9.controller.ControllerExtension
import com.fsck.k9.crypto.EncryptionExtractor
import com.fsck.k9.crypto.openpgp.OpenPgpEncryptionExtractor
import com.fsck.k9.notification.notificationModule
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.ui.addaccount.uiAddAccountModule
import org.koin.core.qualifier.named
import org.koin.dsl.module
private val mainAppModule = module {
single { App.appConfig }
single { MessagingListenerProvider(emptyList()) }
single(named("controllerExtensions")) { emptyList<ControllerExtension>() }
single<EncryptionExtractor> { OpenPgpEncryptionExtractor.newInstance() }
single<StoragePersister> { K9StoragePersister(get()) }
}
val appModules = listOf(
mainAppModule,
notificationModule,
resourcesModule,
backendsModule,
storageModule,
uiAddAccountModule
)

View file

@ -1,5 +0,0 @@
package com.fsck.k9
import com.fsck.k9.controller.MessagingListener
class MessagingListenerProvider(val listeners: List<MessagingListener>)

View file

@ -1,72 +0,0 @@
package com.fsck.k9.backends
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.SystemClock
import com.fsck.k9.backend.imap.SystemAlarmManager
import com.fsck.k9.helper.AlarmManagerCompat
import java.util.concurrent.atomic.AtomicReference
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
private const val ALARM_ACTION = "com.fsck.k9.backends.ALARM"
private const val REQUEST_CODE = 1
private typealias Callback = () -> Unit
class AndroidAlarmManager(
private val context: Context,
private val alarmManager: AlarmManagerCompat,
backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO
) : SystemAlarmManager {
private val coroutineScope = CoroutineScope(backgroundDispatcher)
private val pendingIntent: PendingIntent = run {
val intent = Intent(ALARM_ACTION).apply {
setPackage(context.packageName)
}
val flags = if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flags)
}
private val callback = AtomicReference<Callback?>(null)
init {
val intentFilter = IntentFilter(ALARM_ACTION)
context.registerReceiver(
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val callback = callback.getAndSet(null)
if (callback == null) {
Timber.w("Alarm triggered but 'callback' was null")
} else {
coroutineScope.launch {
callback.invoke()
}
}
}
},
intentFilter
)
}
override fun setAlarm(triggerTime: Long, callback: Callback) {
this.callback.set(callback)
alarmManager.scheduleAlarm(triggerTime, pendingIntent)
}
override fun cancelAlarm() {
callback.set(null)
alarmManager.cancelAlarm(pendingIntent)
}
override fun now(): Long = SystemClock.elapsedRealtime()
}

View file

@ -1,90 +0,0 @@
package com.fsck.k9.backends
import android.content.Context
import android.net.ConnectivityManager
import com.fsck.k9.Account
import com.fsck.k9.backend.BackendFactory
import com.fsck.k9.backend.api.Backend
import com.fsck.k9.backend.imap.ImapBackend
import com.fsck.k9.backend.imap.ImapPushConfigProvider
import com.fsck.k9.mail.NetworkType
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
import com.fsck.k9.mail.power.PowerManager
import com.fsck.k9.mail.ssl.TrustedSocketFactory
import com.fsck.k9.mail.store.imap.IdleRefreshManager
import com.fsck.k9.mail.store.imap.ImapStore
import com.fsck.k9.mail.store.imap.ImapStoreConfig
import com.fsck.k9.mail.transport.smtp.SmtpTransport
import com.fsck.k9.mailstore.K9BackendStorageFactory
import com.fsck.k9.preferences.AccountManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
class ImapBackendFactory(
private val context: Context,
private val accountManager: AccountManager,
private val powerManager: PowerManager,
private val idleRefreshManager: IdleRefreshManager,
private val backendStorageFactory: K9BackendStorageFactory,
private val trustedSocketFactory: TrustedSocketFactory
) : BackendFactory {
override fun createBackend(account: Account): Backend {
val accountName = account.displayName
val backendStorage = backendStorageFactory.createBackendStorage(account)
val imapStore = createImapStore(account)
val pushConfigProvider = createPushConfigProvider(account)
val smtpTransport = createSmtpTransport(account)
return ImapBackend(
accountName,
backendStorage,
imapStore,
powerManager,
idleRefreshManager,
pushConfigProvider,
smtpTransport
)
}
private fun createImapStore(account: Account): ImapStore {
val oAuth2TokenProvider: OAuth2TokenProvider? = null
val config = createImapStoreConfig(account)
return ImapStore.create(
account.incomingServerSettings,
config,
trustedSocketFactory,
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager,
oAuth2TokenProvider
)
}
private fun createImapStoreConfig(account: Account): ImapStoreConfig {
return object : ImapStoreConfig {
override val logLabel
get() = account.description
override fun isSubscribedFoldersOnly() = account.isSubscribedFoldersOnly
override fun useCompression(type: NetworkType) = account.useCompression(type)
}
}
private fun createSmtpTransport(account: Account): SmtpTransport {
val serverSettings = account.outgoingServerSettings
val oauth2TokenProvider: OAuth2TokenProvider? = null
return SmtpTransport(serverSettings, trustedSocketFactory, oauth2TokenProvider)
}
private fun createPushConfigProvider(account: Account) = object : ImapPushConfigProvider {
override val maxPushFoldersFlow: Flow<Int>
get() = accountManager.getAccountFlow(account.uuid)
.map { it.maxPushFolders }
.distinctUntilChanged()
override val idleRefreshMinutesFlow: Flow<Int>
get() = accountManager.getAccountFlow(account.uuid)
.map { it.idleRefreshMinutes }
.distinctUntilChanged()
}
}

View file

@ -1,62 +0,0 @@
package com.fsck.k9.backends
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.account.AccountCreator
import com.fsck.k9.backend.BackendManager
import com.fsck.k9.backend.jmap.JmapDiscoveryResult.JmapAccount
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.FolderType
import com.fsck.k9.mail.ServerSettings
import com.fsck.k9.mailstore.LocalStoreProvider
class JmapAccountCreator(
private val preferences: Preferences,
private val backendManager: BackendManager,
private val accountCreator: AccountCreator,
private val localStoreProvider: LocalStoreProvider
) {
fun createAccount(emailAddress: String, password: String, jmapAccount: JmapAccount) {
val serverSettings = createServerSettings(emailAddress, password, jmapAccount)
val account = preferences.newAccount().apply {
email = emailAddress
description = jmapAccount.name
incomingServerSettings = serverSettings
outgoingServerSettings = serverSettings
chipColor = accountCreator.pickColor()
deletePolicy = Account.DeletePolicy.ON_DELETE
}
createOutboxFolder(account)
preferences.saveAccount(account)
fetchFolderList(account)
}
private fun createServerSettings(emailAddress: String, password: String, jmapAccount: JmapAccount): ServerSettings {
return ServerSettings(
"jmap",
null,
433,
ConnectionSecurity.SSL_TLS_REQUIRED,
AuthType.PLAIN,
emailAddress,
password,
null,
mapOf("accountId" to jmapAccount.accountId)
)
}
private fun createOutboxFolder(account: Account) {
val localStore = localStoreProvider.getInstance(account)
account.outboxFolderId = localStore.createLocalFolder(Account.OUTBOX_NAME, FolderType.OUTBOX)
}
private fun fetchFolderList(account: Account) {
val backend = backendManager.getBackend(account)
backend.refreshFolderList()
}
}

View file

@ -1,28 +0,0 @@
package com.fsck.k9.backends
import com.fsck.k9.Account
import com.fsck.k9.backend.BackendFactory
import com.fsck.k9.backend.api.Backend
import com.fsck.k9.backend.jmap.JmapBackend
import com.fsck.k9.backend.jmap.JmapConfig
import com.fsck.k9.mailstore.K9BackendStorageFactory
class JmapBackendFactory(
private val backendStorageFactory: K9BackendStorageFactory,
private val okHttpClientProvider: OkHttpClientProvider
) : BackendFactory {
override fun createBackend(account: Account): Backend {
val backendStorage = backendStorageFactory.createBackendStorage(account)
val okHttpClient = okHttpClientProvider.getOkHttpClient()
val serverSettings = account.incomingServerSettings
val jmapConfig = JmapConfig(
username = serverSettings.username,
password = serverSettings.password!!,
baseUrl = serverSettings.host,
accountId = serverSettings.extra["accountId"]!!
)
return JmapBackend(backendStorage, okHttpClient, jmapConfig)
}
}

View file

@ -1,39 +0,0 @@
package com.fsck.k9.backends
import com.fsck.k9.backend.BackendManager
import com.fsck.k9.backend.imap.BackendIdleRefreshManager
import com.fsck.k9.backend.imap.SystemAlarmManager
import com.fsck.k9.backend.jmap.JmapAccountDiscovery
import com.fsck.k9.mail.store.imap.IdleRefreshManager
import org.koin.dsl.module
val backendsModule = module {
single {
BackendManager(
mapOf(
"imap" to get<ImapBackendFactory>(),
"pop3" to get<Pop3BackendFactory>(),
"webdav" to get<WebDavBackendFactory>(),
"jmap" to get<JmapBackendFactory>()
)
)
}
single {
ImapBackendFactory(
context = get(),
accountManager = get(),
powerManager = get(),
idleRefreshManager = get(),
backendStorageFactory = get(),
trustedSocketFactory = get()
)
}
single<SystemAlarmManager> { AndroidAlarmManager(context = get(), alarmManager = get()) }
single<IdleRefreshManager> { BackendIdleRefreshManager(alarmManager = get()) }
single { Pop3BackendFactory(get(), get()) }
single { WebDavBackendFactory(get(), get(), get()) }
single { JmapBackendFactory(get(), get()) }
factory { JmapAccountDiscovery() }
factory { JmapAccountCreator(get(), get(), get(), get()) }
single { OkHttpClientProvider() }
}

View file

@ -1,16 +0,0 @@
package com.fsck.k9.backends
import okhttp3.OkHttpClient
class OkHttpClientProvider {
private var okHttpClient: OkHttpClient? = null
@Synchronized
fun getOkHttpClient(): OkHttpClient {
return okHttpClient ?: createOkHttpClient().also { okHttpClient = it }
}
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().build()
}
}

View file

@ -1,35 +0,0 @@
package com.fsck.k9.backends
import com.fsck.k9.Account
import com.fsck.k9.backend.BackendFactory
import com.fsck.k9.backend.api.Backend
import com.fsck.k9.backend.pop3.Pop3Backend
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
import com.fsck.k9.mail.ssl.TrustedSocketFactory
import com.fsck.k9.mail.store.pop3.Pop3Store
import com.fsck.k9.mail.transport.smtp.SmtpTransport
import com.fsck.k9.mailstore.K9BackendStorageFactory
class Pop3BackendFactory(
private val backendStorageFactory: K9BackendStorageFactory,
private val trustedSocketFactory: TrustedSocketFactory
) : BackendFactory {
override fun createBackend(account: Account): Backend {
val accountName = account.displayName
val backendStorage = backendStorageFactory.createBackendStorage(account)
val pop3Store = createPop3Store(account)
val smtpTransport = createSmtpTransport(account)
return Pop3Backend(accountName, backendStorage, pop3Store, smtpTransport)
}
private fun createPop3Store(account: Account): Pop3Store {
val serverSettings = account.incomingServerSettings
return Pop3Store(serverSettings, trustedSocketFactory)
}
private fun createSmtpTransport(account: Account): SmtpTransport {
val serverSettings = account.outgoingServerSettings
val oauth2TokenProvider: OAuth2TokenProvider? = null
return SmtpTransport(serverSettings, trustedSocketFactory, oauth2TokenProvider)
}
}

View file

@ -1,35 +0,0 @@
package com.fsck.k9.backends
import com.fsck.k9.Account
import com.fsck.k9.backend.BackendFactory
import com.fsck.k9.backend.api.Backend
import com.fsck.k9.backend.webdav.WebDavBackend
import com.fsck.k9.mail.ssl.TrustManagerFactory
import com.fsck.k9.mail.store.webdav.DraftsFolderProvider
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
class WebDavBackendFactory(
private val backendStorageFactory: K9BackendStorageFactory,
private val trustManagerFactory: TrustManagerFactory,
private val folderRepository: FolderRepository
) : BackendFactory {
override fun createBackend(account: Account): Backend {
val accountName = account.displayName
val backendStorage = backendStorageFactory.createBackendStorage(account)
val serverSettings = account.incomingServerSettings
val draftsFolderProvider = createDraftsFolderProvider(account)
val webDavStore = WebDavStore(trustManagerFactory, serverSettings, draftsFolderProvider)
val webDavTransport = WebDavTransport(trustManagerFactory, serverSettings, draftsFolderProvider)
return WebDavBackend(accountName, backendStorage, webDavStore, webDavTransport)
}
private fun createDraftsFolderProvider(account: Account): DraftsFolderProvider {
return DraftsFolderProvider {
val draftsFolderId = account.draftsFolderId ?: error("No Drafts folder configured")
folderRepository.getFolderServerId(account, draftsFolderId) ?: error("Couldn't find local Drafts folder")
}
}
}

View file

@ -1,8 +0,0 @@
package com.fsck.k9.glide;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class K9AppGlideModule extends AppGlideModule {
}

View file

@ -1,234 +0,0 @@
package com.fsck.k9.notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.activity.MessageList
import com.fsck.k9.activity.compose.MessageActions
import com.fsck.k9.activity.setup.AccountSetupIncoming
import com.fsck.k9.activity.setup.AccountSetupOutgoing
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.search.LocalSearch
import com.fsck.k9.ui.messagelist.DefaultFolderProvider
import com.fsck.k9.ui.notification.DeleteConfirmationActivity
/**
* This class contains methods to create the [PendingIntent]s for the actions of our notifications.
*
* **Note:**
* We need to take special care to ensure the `PendingIntent`s are unique as defined in the documentation of
* [PendingIntent]. Otherwise selecting a notification action might perform the action on the wrong message.
*
* We use the notification ID as `requestCode` argument to ensure each notification/action pair gets a unique
* `PendingIntent`.
*/
internal class K9NotificationActionCreator(
private val context: Context,
private val defaultFolderProvider: DefaultFolderProvider
) : NotificationActionCreator {
override fun createViewMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = createMessageViewIntent(messageReference)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createViewFolderPendingIntent(account: Account, folderId: Long, notificationId: Int): PendingIntent {
val intent = createMessageListIntent(account, folderId)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createViewMessagesPendingIntent(
account: Account,
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
val folderServerId = getFolderIdOfAllMessages(messageReferences)
val intent = if (folderServerId != null) {
createMessageListIntent(account, folderServerId)
} else {
createMessageListIntent(account)
}
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createViewFolderListPendingIntent(account: Account, notificationId: Int): PendingIntent {
val intent = createMessageListIntent(account)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createDismissAllMessagesPendingIntent(account: Account, notificationId: Int): PendingIntent {
val intent = NotificationActionService.createDismissAllMessagesIntent(context, account)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createDismissMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createDismissMessageIntent(context, messageReference)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createReplyPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent {
val intent = MessageActions.getActionReplyIntent(context, messageReference)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createMarkMessageAsReadPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createMarkMessageAsReadIntent(context, messageReference)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createMarkAllAsReadPendingIntent(
account: Account,
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
val accountUuid = account.uuid
val intent = NotificationActionService.createMarkAllAsReadIntent(context, accountUuid, messageReferences)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun getEditIncomingServerSettingsIntent(account: Account): PendingIntent {
val intent = AccountSetupIncoming.intentActionEditIncomingSettings(context, account)
return PendingIntent.getActivity(context, account.accountNumber, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun getEditOutgoingServerSettingsIntent(account: Account): PendingIntent {
val intent = AccountSetupOutgoing.intentActionEditOutgoingSettings(context, account)
return PendingIntent.getActivity(context, account.accountNumber, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createDeleteMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
createDeleteConfirmationPendingIntent(messageReference, notificationId)
} else {
createDeleteServicePendingIntent(messageReference, notificationId)
}
}
private fun createDeleteServicePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createDeleteMessageIntent(context, messageReference)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun createDeleteConfirmationPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = DeleteConfirmationActivity.getIntent(context, messageReference)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createDeleteAllPendingIntent(
account: Account,
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
getDeleteAllConfirmationPendingIntent(messageReferences, notificationId)
} else {
getDeleteAllServicePendingIntent(account, messageReferences, notificationId)
}
}
private fun getDeleteAllConfirmationPendingIntent(
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
val intent = DeleteConfirmationActivity.getIntent(context, messageReferences)
return PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT)
}
private fun getDeleteAllServicePendingIntent(
account: Account,
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
val accountUuid = account.uuid
val intent = NotificationActionService.createDeleteAllMessagesIntent(context, accountUuid, messageReferences)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createArchiveMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createArchiveMessageIntent(context, messageReference)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createArchiveAllPendingIntent(
account: Account,
messageReferences: List<MessageReference>,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createArchiveAllIntent(context, account, messageReferences)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun createMarkMessageAsSpamPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
val intent = NotificationActionService.createMarkMessageAsSpamIntent(context, messageReference)
return PendingIntent.getService(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun createMessageListIntent(account: Account): Intent {
val folderId = defaultFolderProvider.getDefaultFolder(account)
val search = LocalSearch().apply {
addAllowedFolder(folderId)
addAccountUuid(account.uuid)
}
return MessageList.intentDisplaySearch(
context = context,
search = search,
noThreading = false,
newTask = true,
clearTop = true
)
}
private fun createMessageListIntent(account: Account, folderId: Long): Intent {
val search = LocalSearch().apply {
addAllowedFolder(folderId)
addAccountUuid(account.uuid)
}
return MessageList.intentDisplaySearch(
context = context,
search = search,
noThreading = false,
newTask = true,
clearTop = true
)
}
private fun createMessageViewIntent(message: MessageReference): Intent {
return MessageList.actionDisplayMessageIntent(context, message)
}
private fun getFolderIdOfAllMessages(messageReferences: List<MessageReference>): Long? {
val firstMessage = messageReferences.first()
val folderId = firstMessage.folderId
return if (messageReferences.all { it.folderId == folderId }) folderId else null
}
}

View file

@ -1,100 +0,0 @@
package com.fsck.k9.notification
import android.content.Context
import com.fsck.k9.jmap.R
class K9NotificationResourceProvider(private val context: Context) : NotificationResourceProvider {
override val iconWarning: Int = R.drawable.notification_icon_warning
override val iconMarkAsRead: Int = R.drawable.notification_action_mark_as_read
override val iconDelete: Int = R.drawable.notification_action_delete
override val iconReply: Int = R.drawable.notification_action_reply
override val iconNewMail: Int = R.drawable.notification_icon_new_mail
override val iconSendingMail: Int = R.drawable.notification_icon_check_mail
override val iconCheckingMail: Int = R.drawable.notification_icon_check_mail
override val wearIconMarkAsRead: Int = R.drawable.notification_action_mark_as_read
override val wearIconDelete: Int = R.drawable.notification_action_delete
override val wearIconArchive: Int = R.drawable.notification_action_archive
override val wearIconReplyAll: Int = R.drawable.notification_action_reply
override val wearIconMarkAsSpam: Int = R.drawable.notification_action_mark_as_spam
override val pushChannelName: String
get() = context.getString(R.string.notification_channel_push_title)
override val pushChannelDescription: String
get() = context.getString(R.string.notification_channel_push_description)
override val messagesChannelName: String
get() = context.getString(R.string.notification_channel_messages_title)
override val messagesChannelDescription: String
get() = context.getString(R.string.notification_channel_messages_description)
override val miscellaneousChannelName: String
get() = context.getString(R.string.notification_channel_miscellaneous_title)
override val miscellaneousChannelDescription: String
get() = context.getString(R.string.notification_channel_miscellaneous_description)
override fun authenticationErrorTitle(): String =
context.getString(R.string.notification_authentication_error_title)
override fun authenticationErrorBody(accountName: String): String =
context.getString(R.string.notification_authentication_error_text, accountName)
override fun certificateErrorTitle(): String = context.getString(R.string.notification_certificate_error_public)
override fun certificateErrorTitle(accountName: String): String =
context.getString(R.string.notification_certificate_error_title, accountName)
override fun certificateErrorBody(): String = context.getString(R.string.notification_certificate_error_text)
override fun newMailTitle(): String = context.getString(R.string.notification_new_title)
override fun newMailUnreadMessageCount(unreadMessageCount: Int, accountName: String): String =
context.getString(R.string.notification_new_one_account_fmt, unreadMessageCount, accountName)
override fun newMessagesTitle(newMessagesCount: Int): String =
context.resources.getQuantityString(
R.plurals.notification_new_messages_title,
newMessagesCount, newMessagesCount
)
override fun additionalMessages(overflowMessagesCount: Int, accountName: String): String =
context.getString(R.string.notification_additional_messages, overflowMessagesCount, accountName)
override fun previewEncrypted(): String = context.getString(R.string.preview_encrypted)
override fun noSubject(): String = context.getString(R.string.general_no_subject)
override fun recipientDisplayName(recipientDisplayName: String): String =
context.getString(R.string.message_to_fmt, recipientDisplayName)
override fun noSender(): String = context.getString(R.string.general_no_sender)
override fun sendFailedTitle(): String = context.getString(R.string.send_failure_subject)
override fun sendingMailTitle(): String = context.getString(R.string.notification_bg_send_title)
override fun sendingMailBody(accountName: String): String =
context.getString(R.string.notification_bg_send_ticker, accountName)
override fun checkingMailTicker(accountName: String, folderName: String): String =
context.getString(R.string.notification_bg_sync_ticker, accountName, folderName)
override fun checkingMailTitle(): String =
context.getString(R.string.notification_bg_sync_title)
override fun checkingMailSeparator(): String =
context.getString(R.string.notification_bg_title_separator)
override fun actionMarkAsRead(): String = context.getString(R.string.notification_action_mark_as_read)
override fun actionMarkAllAsRead(): String = context.getString(R.string.notification_action_mark_all_as_read)
override fun actionDelete(): String = context.getString(R.string.notification_action_delete)
override fun actionDeleteAll(): String = context.getString(R.string.notification_action_delete_all)
override fun actionReply(): String = context.getString(R.string.notification_action_reply)
override fun actionArchive(): String = context.getString(R.string.notification_action_archive)
override fun actionArchiveAll(): String = context.getString(R.string.notification_action_archive_all)
override fun actionMarkAsSpam(): String = context.getString(R.string.notification_action_spam)
}

View file

@ -1,69 +0,0 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.helper.Contacts
import com.fsck.k9.mail.Flag
import com.fsck.k9.mailstore.LocalFolder
import com.fsck.k9.mailstore.LocalMessage
class K9NotificationStrategy(val contacts: Contacts) : NotificationStrategy {
override fun shouldNotifyForMessage(
account: Account,
localFolder: LocalFolder,
message: LocalMessage,
isOldMessage: Boolean
): Boolean {
// If we don't even have an account name, don't show the notification.
// (This happens during initial account setup)
if (account.name == null) {
return false
}
if (K9.isQuietTime && !K9.isNotificationDuringQuietTimeEnabled) {
return false
}
// Do not notify if the user does not have notifications enabled or if the message has
// been read.
if (!account.isNotifyNewMail || message.isSet(Flag.SEEN) || isOldMessage) {
return false
}
val aDisplayMode = account.folderDisplayMode
val aNotifyMode = account.folderNotifyNewMailMode
val fDisplayClass = localFolder.displayClass
val fNotifyClass = localFolder.notifyClass
if (LocalFolder.isModeMismatch(aDisplayMode, fDisplayClass)) {
// Never notify a folder that isn't displayed
return false
}
if (LocalFolder.isModeMismatch(aNotifyMode, fNotifyClass)) {
// Do not notify folders in the wrong class
return false
}
// No notification for new messages in Trash, Drafts, Spam or Sent folder.
val folder = message.folder
if (folder != null) {
val folderId = folder.databaseId
if (folderId == account.trashFolderId ||
folderId == account.draftsFolderId ||
folderId == account.spamFolderId ||
folderId == account.sentFolderId
) {
return false
}
}
// Don't notify if the sender address matches one of our identities and the user chose not
// to be notified for such messages.
return if (account.isAnIdentity(message.from) && !account.isNotifySelfNewMail) {
false
} else !account.isNotifyContactsMailOnly || contacts.isAnyInContacts(message.from)
}
}

View file

@ -1,9 +0,0 @@
package com.fsck.k9.notification
import org.koin.dsl.module
val notificationModule = module {
single<NotificationActionCreator> { K9NotificationActionCreator(context = get(), defaultFolderProvider = get()) }
single<NotificationResourceProvider> { K9NotificationResourceProvider(get()) }
single<NotificationStrategy> { K9NotificationStrategy(get()) }
}

View file

@ -1,10 +0,0 @@
package com.fsck.k9.resources
import android.content.Context
import com.fsck.k9.autocrypt.AutocryptStringProvider
import com.fsck.k9.jmap.R
class K9AutocryptStringProvider(private val context: Context) : AutocryptStringProvider {
override fun transferMessageSubject(): String = context.getString(R.string.ac_transfer_msg_subject)
override fun transferMessageBody(): String = context.getString(R.string.ac_transfer_msg_body)
}

View file

@ -1,52 +0,0 @@
package com.fsck.k9.resources
import android.content.Context
import com.fsck.k9.CoreResourceProvider
import com.fsck.k9.jmap.R
import com.fsck.k9.notification.PushNotificationState
class K9CoreResourceProvider(private val context: Context) : CoreResourceProvider {
override fun defaultSignature(): String = context.getString(R.string.default_signature)
override fun defaultIdentityDescription(): String = context.getString(R.string.default_identity_description)
override fun contactDisplayNamePrefix(): String = context.getString(R.string.message_to_label)
override fun contactUnknownSender(): String = context.getString(R.string.unknown_sender)
override fun contactUnknownRecipient(): String = context.getString(R.string.unknown_recipient)
override fun messageHeaderFrom(): String = context.getString(R.string.message_compose_quote_header_from)
override fun messageHeaderTo(): String = context.getString(R.string.message_compose_quote_header_to)
override fun messageHeaderCc(): String = context.getString(R.string.message_compose_quote_header_cc)
override fun messageHeaderDate(): String = context.getString(R.string.message_compose_quote_header_send_date)
override fun messageHeaderSubject(): String = context.getString(R.string.message_compose_quote_header_subject)
override fun messageHeaderSeparator(): String = context.getString(R.string.message_compose_quote_header_separator)
override fun noSubject(): String = context.getString(R.string.general_no_subject)
override fun userAgent(): String = context.getString(R.string.message_header_mua)
override fun encryptedSubject(): String = context.getString(R.string.encrypted_subject)
override fun replyHeader(sender: String): String =
context.getString(R.string.message_compose_reply_header_fmt, sender)
override fun replyHeader(sender: String, sentDate: String): String =
context.getString(R.string.message_compose_reply_header_fmt_with_date, sentDate, sender)
override fun searchUnifiedInboxTitle(): String = context.getString(R.string.integrated_inbox_title)
override fun searchUnifiedInboxDetail(): String = context.getString(R.string.integrated_inbox_detail)
override fun outboxFolderName(): String = context.getString(R.string.special_mailbox_name_outbox)
override val iconPushNotification: Int = R.drawable.ic_push_notification
override fun pushNotificationText(notificationState: PushNotificationState): String {
val resId = when (notificationState) {
PushNotificationState.INITIALIZING -> R.string.push_notification_state_initializing
PushNotificationState.LISTENING -> R.string.push_notification_state_listening
PushNotificationState.WAIT_BACKGROUND_SYNC -> R.string.push_notification_state_wait_background_sync
PushNotificationState.WAIT_NETWORK -> R.string.push_notification_state_wait_network
}
return context.getString(resId)
}
override fun pushNotificationInfoText(): String = context.getString(R.string.push_notification_info)
}

View file

@ -1,10 +0,0 @@
package com.fsck.k9.resources
import com.fsck.k9.CoreResourceProvider
import com.fsck.k9.autocrypt.AutocryptStringProvider
import org.koin.dsl.module
val resourcesModule = module {
single<CoreResourceProvider> { K9CoreResourceProvider(get()) }
single<AutocryptStringProvider> { K9AutocryptStringProvider(get()) }
}

View file

@ -1,21 +0,0 @@
package com.fsck.k9.ui
import android.view.View
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter
import com.google.android.material.textfield.TextInputLayout
@BindingAdapter("isVisible")
fun setVisibility(view: View, value: Boolean) {
view.isVisible = value
}
@BindingAdapter("error")
fun setError(view: TextInputLayout, value: Int?) {
if (value == null) {
view.error = null
} else {
val errorString = view.context.getString(value)
view.error = errorString
}
}

View file

@ -1,31 +0,0 @@
package com.fsck.k9.ui.addaccount
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import com.fsck.k9.jmap.R
import com.fsck.k9.ui.base.K9Activity
import com.fsck.k9.ui.base.extensions.findNavController
class AddAccountActivity : K9Activity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setLayout(R.layout.activity_add_account)
initializeActionBar()
}
private fun initializeActionBar() {
val appBarConfiguration = AppBarConfiguration(topLevelDestinationIds = setOf(R.id.addJmapAccountScreen))
navController = findNavController(R.id.nav_host_fragment)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}

View file

@ -1,39 +0,0 @@
package com.fsck.k9.ui.addaccount
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.fsck.k9.jmap.R
import com.fsck.k9.jmap.databinding.FragmentAddAccountBinding
import com.fsck.k9.ui.observeNotNull
import org.koin.androidx.viewmodel.ext.android.viewModel
class AddAccountFragment : Fragment() {
private val viewModel: AddAccountViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.getActionEvents().observeNotNull(this) { handleActionEvents(it) }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentAddAccountBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.viewModel = viewModel
return binding.root
}
private fun handleActionEvents(action: Action) {
when (action) {
is Action.GoToMessageList -> goToMessageList()
}
}
private fun goToMessageList() {
findNavController().navigate(R.id.action_addJmapAccountScreen_to_messageListScreen)
}
}

View file

@ -1,157 +0,0 @@
package com.fsck.k9.ui.addaccount
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fsck.k9.EmailAddressValidator
import com.fsck.k9.backend.jmap.JmapAccountDiscovery
import com.fsck.k9.backend.jmap.JmapDiscoveryResult
import com.fsck.k9.backend.jmap.JmapDiscoveryResult.JmapAccount
import com.fsck.k9.backends.JmapAccountCreator
import com.fsck.k9.helper.SingleLiveEvent
import com.fsck.k9.helper.measureRealtimeMillisWithResult
import com.fsck.k9.jmap.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AddAccountViewModel(
private val emailAddressValidator: EmailAddressValidator,
private val jmapAccountDiscovery: JmapAccountDiscovery,
private val jmapAccountCreator: JmapAccountCreator
) : ViewModel() {
val emailAddress = MutableLiveData<String>()
val emailAddressError = MutableLiveData<Int?>()
val password = MutableLiveData<String>()
val passwordError = MutableLiveData<Int?>()
val setupErrorText: MutableLiveData<Int> = createMutableLiveData(R.string.empty_string)
val isInputEnabled: MutableLiveData<Boolean> = createMutableLiveData(true)
val isNextButtonEnabled: MutableLiveData<Boolean> = createMutableLiveData(true)
val isProgressBarVisible: MutableLiveData<Boolean> = createMutableLiveData(false)
private val actionLiveData = SingleLiveEvent<Action>()
init {
Transformations.distinctUntilChanged(emailAddress).observeForever { resetEmailAddressError() }
Transformations.distinctUntilChanged(password).observeForever { resetPasswordError() }
}
fun getActionEvents(): LiveData<Action> = actionLiveData
fun onNextButtonClicked() {
discoverServerSettings()
}
private fun discoverServerSettings() {
val emailAddress = this.emailAddress.value?.trim() ?: ""
val password = this.password.value ?: ""
if (!emailAddressValidator.isValidAddressOnly(emailAddress)) {
displayEmailAddressError(R.string.add_account__email_address_error)
return
}
showDiscoveryProgressBar()
viewModelScope.launch {
val (elapsed, discoveryResult) = measureRealtimeMillisWithResult {
withContext(Dispatchers.IO) {
jmapAccountDiscovery.discover(emailAddress, password)
}
}
if (elapsed < MIN_PROGRESS_DURATION) {
delay(MIN_PROGRESS_DURATION - elapsed)
}
if (discoveryResult is JmapAccount) {
createAccount(emailAddress, password, discoveryResult)
} else {
displayDiscoveryError(discoveryResult)
hideDiscoveryProgressBar()
}
}
}
private suspend fun createAccount(emailAddress: String, password: String, jmapAccount: JmapAccount) {
GlobalScope.launch(Dispatchers.IO) {
jmapAccountCreator.createAccount(emailAddress, password, jmapAccount)
}.join()
sendActionEvent(Action.GoToMessageList)
}
private fun displayDiscoveryError(discoveryResult: JmapDiscoveryResult) {
when (discoveryResult) {
is JmapDiscoveryResult.GenericFailure -> {
displayError(R.string.add_account__generic_failure)
}
is JmapDiscoveryResult.NoEmailAccountFoundFailure -> {
displayError(R.string.add_account__no_email_account_found)
}
is JmapDiscoveryResult.AuthenticationFailure -> {
displayPasswordError(R.string.add_account__password_error)
}
is JmapDiscoveryResult.EndpointNotFoundFailure -> {
displayError(R.string.add_account__jmap_server_not_found)
}
}
}
@Suppress("SameParameterValue")
private fun displayEmailAddressError(@StringRes error: Int) {
emailAddressError.value = error
}
private fun resetEmailAddressError() {
emailAddressError.value = null
}
@Suppress("SameParameterValue")
private fun displayPasswordError(@StringRes error: Int) {
passwordError.value = error
}
private fun resetPasswordError() {
passwordError.value = null
}
private fun showDiscoveryProgressBar() {
isInputEnabled.value = false
isProgressBarVisible.value = true
isNextButtonEnabled.value = false
setupErrorText.value = R.string.empty_string
}
private fun hideDiscoveryProgressBar() {
isInputEnabled.value = true
isProgressBarVisible.value = false
isNextButtonEnabled.value = true
}
private fun displayError(@StringRes error: Int) {
setupErrorText.value = error
}
private fun sendActionEvent(action: Action) {
actionLiveData.value = action
}
private fun <T> createMutableLiveData(initialValue: T): MutableLiveData<T> {
return MutableLiveData<T>().apply {
value = initialValue
}
}
companion object {
private const val MIN_PROGRESS_DURATION = 500
}
}
sealed class Action {
object GoToMessageList : Action()
}

View file

@ -1,8 +0,0 @@
package com.fsck.k9.ui.addaccount
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val uiAddAccountModule = module {
viewModel { AddAccountViewModel(get(), get(), get()) }
}

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_add_account" />
</LinearLayout>

View file

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.fsck.k9.ui.addaccount.AddAccountViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailAddressLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:error="@{viewModel.emailAddressError}"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/emailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{viewModel.isInputEnabled}"
android:hint="@string/account_setup_basics_email_hint"
android:imeOptions="flagNoExtractUi"
android:inputType="textEmailAddress"
android:text="@={viewModel.emailAddress}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailPasswordLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:error="@{viewModel.passwordError}"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailAddressLayout"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/emailPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{viewModel.isInputEnabled}"
android:hint="@string/account_setup_basics_password_hint"
android:imeOptions="flagNoExtractUi"
android:inputType="textPassword"
android:text="@={viewModel.password}" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/setupError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.setupErrorText}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailPasswordLayout" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
app:isVisible="@{viewModel.isProgressBarVisible()}"
app:layout_constraintBottom_toTopOf="@+id/nextButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/setupError" />
<Button
android:id="@+id/nextButton"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:enabled="@{viewModel.isNextButtonEnabled()}"
android:onClick="@{() -> viewModel.onNextButtonClicked()}"
android:text="@string/next_action"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailPasswordLayout"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation_add_account"
app:startDestination="@id/addJmapAccountScreen">
<fragment
android:id="@+id/addJmapAccountScreen"
android:name="com.fsck.k9.ui.addaccount.AddAccountFragment"
android:label="@string/add_account_action"
tools:layout="@layout/fragment_add_account">
<action
android:id="@+id/action_addJmapAccountScreen_to_messageListScreen"
app:destination="@id/messageListScreen" />
</fragment>
<activity
android:id="@+id/messageListScreen"
android:name="com.fsck.k9.activity.MessageList"
tools:layout="@layout/message_list"/>
</navigation>

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation_onboarding"
app:startDestination="@id/welcomeScreen">
<fragment
android:id="@+id/welcomeScreen"
android:name="com.fsck.k9.ui.onboarding.WelcomeFragment"
android:label="@string/welcome_message_title"
tools:layout="@layout/fragment_welcome_message">
<action
android:id="@+id/action_welcomeScreen_to_settingsImportScreen"
app:destination="@id/settingsImportScreen" />
<action
android:id="@+id/action_welcomeScreen_to_addAccountScreen"
app:destination="@id/addAccountScreen" />
<action
android:id="@+id/action_welcomeScreen_to_messageListScreen"
app:destination="@id/messageListScreen" />
</fragment>
<fragment
android:id="@+id/settingsImportScreen"
android:name="com.fsck.k9.ui.settings.import.SettingsImportFragment"
android:label="@string/settings_import_title"
tools:layout="@layout/fragment_settings_import"/>
<activity
android:id="@+id/addAccountScreen"
android:name="com.fsck.k9.ui.addaccount.AddAccountActivity"
android:label="@string/add_account_action"
tools:layout="@layout/activity_add_account"/>
<activity
android:id="@+id/messageListScreen"
android:name="com.fsck.k9.activity.MessageList"
tools:layout="@layout/message_list"/>
</navigation>

View file

@ -1,11 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">K-9 JMAP</string>
<string name="empty_string" translatable="false" />
<string name="add_account__email_address_error">Please enter a valid email address</string>
<string name="add_account__password_error">"Couldn't sign in. Please make sure your password is correct."</string>
<string name="add_account__generic_failure">Unknown error while discovering server settings</string>
<string name="add_account__no_email_account_found">"Couldn't find an email account on the JMAP server"</string>
<string name="add_account__jmap_server_not_found">"Couldn't find the JMAP server from your email address"</string>
</resources>

View file

@ -1,47 +0,0 @@
package com.fsck.k9
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.fsck.k9.ui.changelog.ChangeLogMode
import com.fsck.k9.ui.changelog.ChangelogViewModel
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferPresenter
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.helper.SizeFormatter
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.logger.PrintLogger
import org.koin.core.parameter.parametersOf
import org.koin.java.KoinJavaComponent
import org.koin.test.AutoCloseKoinTest
import org.koin.test.check.checkModules
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.openintents.openpgp.OpenPgpApiManager
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(application = App::class)
class DependencyInjectionTest : AutoCloseKoinTest() {
val lifecycleOwner = mock<LifecycleOwner> {
on { lifecycle } doReturn mock<Lifecycle>()
}
val autocryptTransferView = mock<AutocryptKeyTransferActivity>()
@KoinInternalApi
@Test
fun testDependencyTree() {
KoinJavaComponent.getKoin().setupLogger(PrintLogger())
getKoin().checkModules {
withParameter<OpenPgpApiManager> { lifecycleOwner }
create<AutocryptKeyTransferPresenter> { parametersOf(lifecycleOwner, autocryptTransferView) }
withParameter<FolderNameFormatter> { RuntimeEnvironment.application }
withParameter<SizeFormatter> { RuntimeEnvironment.application }
withParameter<ChangelogViewModel> { ChangeLogMode.CHANGE_LOG }
}
}
}

View file

@ -1,5 +1,4 @@
include ':app:k9mail'
include ':app:k9mail-jmap'
include ':app:ui:base'
include ':app:ui:setup'
include ':app:ui:legacy'