Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
Crash because WorkManager isn't initialized properly
Stack trace retrieved via Google Play Console:

```text
Exception java.lang.RuntimeException: Unable to create service androidx.work.impl.background.systemjob.SystemJobService: java.lang.IllegalStateException: WorkManager needs to be initialized via a ContentProvider#onCreate() or an Application#onCreate().
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:4355)
  at android.app.ActivityThread.access$1800 (ActivityThread.java:261)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2044)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loop (Looper.java:236)
  at android.app.ActivityThread.main (ActivityThread.java:8051)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:620)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1011)
Caused by java.lang.IllegalStateException: WorkManager needs to be initialized via a ContentProvider#onCreate() or an Application#onCreate().
  at androidx.work.impl.background.systemjob.SystemJobService.onCreate (SystemJobService.java:73)
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:4343)
```


Discussion hints:
nan

Pull request: Change `WorkManager` initialization
This should fix #6553.

Test command: ./gradlew :app:k9mail:testDebugUnitTest --tests "com.fsck.k9.DependencyInjectionTest"

Fix patch:
diff --git a/app/core/src/main/java/com/fsck/k9/job/K9WorkerFactory.kt b/app/core/src/main/java/com/fsck/k9/job/K9WorkerFactory.kt
index adcb9258812..71af4cbf3da 100644
--- a/app/core/src/main/java/com/fsck/k9/job/K9WorkerFactory.kt
+++ b/app/core/src/main/java/com/fsck/k9/job/K9WorkerFactory.kt
@@ -4,23 +4,16 @@ import android.content.Context
 import androidx.work.ListenableWorker
 import androidx.work.WorkerFactory
 import androidx.work.WorkerParameters
-import com.fsck.k9.Preferences
-import com.fsck.k9.controller.MessagingController
+import org.koin.core.parameter.parametersOf
+import org.koin.java.KoinJavaComponent.getKoin
 
-class K9WorkerFactory(
-    private val messagingController: MessagingController,
-    private val preferences: Preferences
-) : WorkerFactory() {
+class K9WorkerFactory : WorkerFactory() {
     override fun createWorker(
         appContext: Context,
         workerClassName: String,
         workerParameters: WorkerParameters
-    ): ListenableWorker? {
-        return when (workerClassName) {
-            MailSyncWorker::class.java.canonicalName -> {
-                MailSyncWorker(messagingController, preferences, appContext, workerParameters)
-            }
-            else -> null
-        }
+    ): ListenableWorker {
+        val workerClass = Class.forName(workerClassName).kotlin
+        return getKoin().get(workerClass) { parametersOf(workerParameters) }
     }
 }
diff --git a/app/core/src/main/java/com/fsck/k9/job/KoinModule.kt b/app/core/src/main/java/com/fsck/k9/job/KoinModule.kt
index 4dd6f0e8fa3..8158ae494b4 100644
--- a/app/core/src/main/java/com/fsck/k9/job/KoinModule.kt
+++ b/app/core/src/main/java/com/fsck/k9/job/KoinModule.kt
@@ -1,12 +1,18 @@
 package com.fsck.k9.job
 
+import android.content.Context
+import androidx.work.WorkManager
 import androidx.work.WorkerFactory
+import androidx.work.WorkerParameters
 import org.koin.dsl.module
 
 val jobModule = module {
-    single { WorkManagerProvider(get(), get()) }
-    single<WorkerFactory> { K9WorkerFactory(get(), get()) }
-    single { get<WorkManagerProvider>().getWorkManager() }
-    single { K9JobManager(get(), get(), get()) }
+    single { WorkManagerConfigurationProvider(workerFactory = get()) }
+    single<WorkerFactory> { K9WorkerFactory() }
+    single { WorkManager.getInstance(get<Context>()) }
+    single { K9JobManager(workManager = get(), preferences = get(), mailSyncWorkerManager = get()) }
     factory { MailSyncWorkerManager(workManager = get(), clock = get()) }
+    factory { (parameters: WorkerParameters) ->
+        MailSyncWorker(messagingController = get(), preferences = get(), context = get(), parameters)
+    }
 }
diff --git a/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt b/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt
new file mode 100644
index 00000000000..54d973fee18
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt
@@ -0,0 +1,12 @@
+package com.fsck.k9.job
+
+import androidx.work.Configuration
+import androidx.work.WorkerFactory
+
+class WorkManagerConfigurationProvider(private val workerFactory: WorkerFactory) {
+    fun getConfiguration(): Configuration {
+        return Configuration.Builder()
+            .setWorkerFactory(workerFactory)
+            .build()
+    }
+}
diff --git a/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt b/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt
deleted file mode 100644
index f31fbaaf7b2..00000000000
--- a/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.fsck.k9.job
-
-import android.content.Context
-import androidx.work.Configuration
-import androidx.work.WorkManager
-import androidx.work.WorkerFactory
-
-class WorkManagerProvider(private val context: Context, private val workerFactory: WorkerFactory) {
-    fun getWorkManager(): WorkManager {
-        val configuration = Configuration.Builder()
-            .setWorkerFactory(workerFactory)
-            .build()
-
-        WorkManager.initialize(context, configuration)
-
-        return WorkManager.getInstance(context)
-    }
-}
diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle
index cbd49fe57f3..2c55af72ea9 100644
--- a/app/k9mail/build.gradle
+++ b/app/k9mail/build.gradle
@@ -18,6 +18,7 @@ dependencies {
 
     implementation "androidx.appcompat:appcompat:${versions.androidxAppCompat}"
     implementation "androidx.core:core-ktx:${versions.androidxCore}"
+    implementation "androidx.work:work-runtime-ktx:${versions.androidxWorkManager}"
     implementation "com.takisoft.preferencex:preferencex:${versions.preferencesFix}"
     implementation "com.jakewharton.timber:timber:${versions.timber}"
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinCoroutines}"
diff --git a/app/k9mail/src/main/java/com/fsck/k9/App.kt b/app/k9mail/src/main/java/com/fsck/k9/App.kt
index 00821045afc..7f87dcfe2bc 100644
--- a/app/k9mail/src/main/java/com/fsck/k9/App.kt
+++ b/app/k9mail/src/main/java/com/fsck/k9/App.kt
@@ -7,6 +7,7 @@ import app.k9mail.ui.widget.list.MessageListWidgetManager
 import com.fsck.k9.activity.LauncherShortcuts
 import com.fsck.k9.activity.MessageCompose
 import com.fsck.k9.controller.MessagingController
+import com.fsck.k9.job.WorkManagerConfigurationProvider
 import com.fsck.k9.notification.NotificationChannelManager
 import com.fsck.k9.provider.UnreadWidgetProvider
 import com.fsck.k9.ui.base.AppLanguageManager
@@ -24,14 +25,17 @@ import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.plus
 import org.koin.android.ext.android.inject
 import timber.log.Timber
+import androidx.work.Configuration as WorkManagerConfiguration
 
-class App : Application() {
+class App : Application(), WorkManagerConfiguration.Provider {
     private val messagingController: MessagingController by inject()
     private val messagingListenerProvider: MessagingListenerProvider by inject()
     private val themeManager: ThemeManager by inject()
     private val appLanguageManager: AppLanguageManager by inject()
     private val notificationChannelManager: NotificationChannelManager by inject()
     private val messageListWidgetManager: MessageListWidgetManager by inject()
+    private val workManagerConfigurationProvider: WorkManagerConfigurationProvider by inject()
+
     private val appCoroutineScope: CoroutineScope = GlobalScope + Dispatchers.Main
     private var appLanguageManagerInitialized = false
 
@@ -124,6 +128,10 @@ class App : Application() {
             .launchIn(appCoroutineScope)
     }
 
+    override fun getWorkManagerConfiguration(): WorkManagerConfiguration {
+        return workManagerConfigurationProvider.getConfiguration()
+    }
+
     companion object {
         val appConfig = AppConfig(
             componentsToDisable = listOf(


Test patch:
diff --git a/app/core/src/test/java/com/fsck/k9/TestApp.kt b/app/core/src/test/java/com/fsck/k9/TestApp.kt
index b365bd4ddc5..7f6f0f88677 100644
--- a/app/core/src/test/java/com/fsck/k9/TestApp.kt
+++ b/app/core/src/test/java/com/fsck/k9/TestApp.kt
@@ -1,6 +1,7 @@
 package com.fsck.k9
 
 import android.app.Application
+import androidx.work.WorkManager
 import com.fsck.k9.backend.BackendManager
 import com.fsck.k9.controller.ControllerExtension
 import com.fsck.k9.crypto.EncryptionExtractor
@@ -36,4 +37,5 @@ val testModule = module {
     single { mock<NotificationActionCreator>() }
     single { mock<NotificationStrategy>() }
     single(named("controllerExtensions")) { emptyList<ControllerExtension>() }
+    single { mock<WorkManager>() }
 }
diff --git a/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt b/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt
index f9da2d72cae..09f3508db35 100644
--- a/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt
+++ b/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt
@@ -1,6 +1,8 @@
 package com.fsck.k9
 
 import androidx.lifecycle.LifecycleOwner
+import androidx.work.WorkerParameters
+import com.fsck.k9.job.MailSyncWorker
 import com.fsck.k9.ui.changelog.ChangeLogMode
 import com.fsck.k9.ui.changelog.ChangelogViewModel
 import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
@@ -41,6 +43,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
             withParameter<FolderNameFormatter> { RuntimeEnvironment.getApplication() }
             withParameter<SizeFormatter> { RuntimeEnvironment.getApplication() }
             withParameter<ChangelogViewModel> { ChangeLogMode.CHANGE_LOG }
+            withParameter<MailSyncWorker> { mock<WorkerParameters>() }
         }
     }
 }