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

Issue:
Add support for ID command to support 163.com
**Is your feature request related to a problem? Please describe.**
I got rejected receiving mails using imap. I was trying to log in to 163 email server when this happens.

**Describe the solution you'd like**
I was told that the email client app should return its identity to allow connecting to the email server.

The developer of that email server provides the following code:
```
Properties props = new Properties(); 
props.setProperty("mail.store.protocol", "imap"); 
props.setProperty("mail.imap.host", "imap.163.com"); 
props.setProperty("mail.imap.port", "143"); 

HashMap IAM = new HashMap();
//IMAP ID: key, value pair. E.g. name，version，vendor，support-email
IAM.put("name","myname");      
IAM.put("version","1.0.0");      
IAM.put("vendor","myclient");      
IAM.put("support-email","testmail@test.com");  
Session session = Session.getInstance(props); 

IMAPStore store = (IMAPStore) session.getStore("imap");
//user account and password
store.connect("testmail@163.com", "password"); 

store.id(IAM);
```

The developer provides the following link to explain context:
https://javaee.github.io/javamail/docs/api/index.html?com/sun/mail/imap/IMAPStore.html

Could you evaluate if this situation is a valid case to make it more secure/compatible with imap server?


Discussion hints:
This code can be used to fool 163 before k9 supports the id command,

com.fsck.k9.mail.store.imap.RealImapConnection#open
```
            if (hasCapability("ID")) {
                executeSimpleCommand("ID (\"name\" \"k-9\" \"version\" \"6.000\")");
            }
```
![image](https://user-images.githubusercontent.com/11682198/176149048-19cc6e26-a606-4cd8-b9b9-e9d91554e45d.png)


@AoEiuV020 Could you please make a PR for this? I think it's helpful since a large number of people use 163 mails in China.
The custom option of [FairEmail](https://github.com/M66B/FairEmail/) supports 163.com.Although FairEmail is not listed in the support list, the custom option is available after adding 163.com.Can it be used as a reference?

Pull request: Add ID command for IMAP
Fixes #5089
This PR is based on the implementation of `useCompression` and PR #5578.
I have tested this PR on Netease mail, which requires the ID extension. It works as expected.

Test command: ./gradlew :mail:protocols:imap:test --tests "com.fsck.k9.mail.store.imap.RealImapStoreTest" --tests "com.fsck.k9.mail.store.imap.RealImapConnectionTest"

Fix patch:
diff --git a/app/core/src/main/java/com/fsck/k9/Account.kt b/app/core/src/main/java/com/fsck/k9/Account.kt
index b5ad45f9c8d..0c585eb0f91 100644
--- a/app/core/src/main/java/com/fsck/k9/Account.kt
+++ b/app/core/src/main/java/com/fsck/k9/Account.kt
@@ -232,6 +232,10 @@ class Account(override val uuid: String) : BaseAccount {
     @set:Synchronized
     var useCompression = true
 
+    @get:Synchronized
+    @set:Synchronized
+    var isSendClientIdEnabled = true
+
     @get:Synchronized
     @set:Synchronized
     var searchableFolders = Searchable.ALL
diff --git a/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt b/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt
index e78a2ca2ea1..723e2fc9833 100644
--- a/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt
+++ b/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt
@@ -140,6 +140,7 @@ class AccountPreferenceSerializer(
             isReplyAfterQuote = storage.getBoolean("$accountUuid.replyAfterQuote", DEFAULT_REPLY_AFTER_QUOTE)
             isStripSignature = storage.getBoolean("$accountUuid.stripSignature", DEFAULT_STRIP_SIGNATURE)
             useCompression = storage.getBoolean("$accountUuid.useCompression", true)
+            isSendClientIdEnabled = storage.getBoolean("$accountUuid.sendClientId", true)
 
             importedAutoExpandFolder = storage.getString("$accountUuid.autoExpandFolderName", null)
 
@@ -364,6 +365,7 @@ class AccountPreferenceSerializer(
             editor.putLong("$accountUuid.lastFolderListRefreshTime", lastFolderListRefreshTime)
             editor.putBoolean("$accountUuid.isFinishedSetup", isFinishedSetup)
             editor.putBoolean("$accountUuid.useCompression", useCompression)
+            editor.putBoolean("$accountUuid.sendClientId", isSendClientIdEnabled)
             editor.putBoolean("$accountUuid.migrateToOAuth", shouldMigrateToOAuth)
         }
 
@@ -483,6 +485,7 @@ class AccountPreferenceSerializer(
         editor.remove("$accountUuid.lastFolderListRefreshTime")
         editor.remove("$accountUuid.isFinishedSetup")
         editor.remove("$accountUuid.useCompression")
+        editor.remove("$accountUuid.sendClientId")
         editor.remove("$accountUuid.migrateToOAuth")
 
         deleteIdentities(account, storage, editor)
diff --git a/app/core/src/main/java/com/fsck/k9/preferences/AccountSettingsDescriptions.java b/app/core/src/main/java/com/fsck/k9/preferences/AccountSettingsDescriptions.java
index 97fcd961d6e..4cbb8db625a 100644
--- a/app/core/src/main/java/com/fsck/k9/preferences/AccountSettingsDescriptions.java
+++ b/app/core/src/main/java/com/fsck/k9/preferences/AccountSettingsDescriptions.java
@@ -276,6 +276,9 @@ public class AccountSettingsDescriptions {
         s.put("useCompression", Settings.versions(
                 new V(81, new BooleanSetting(true))
         ));
+        s.put("sendClientId", Settings.versions(
+                new V(88, new BooleanSetting(true))
+        ));
         // note that there is no setting for openPgpProvider, because this will have to be set up together
         // with the actual provider after import anyways.
 
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 ea45b4d983b..1aa80b3a590 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 = 87;
+    public static final int VERSION = 88;
 
     static Map<String, Object> validate(int version, Map<String, TreeMap<Integer, SettingsDescription>> settings,
             Map<String, String> importedSettings, boolean useDefaultValues) {
diff --git a/app/k9mail/build.gradle.kts b/app/k9mail/build.gradle.kts
index 1aab929d9ee..bcb459a1705 100644
--- a/app/k9mail/build.gradle.kts
+++ b/app/k9mail/build.gradle.kts
@@ -57,6 +57,8 @@ android {
                 "fy",
             ),
         )
+
+        buildConfigField("String", "CLIENT_ID_APP_NAME", "\"K-9 Mail\"")
     }
 
     signingConfigs {
diff --git a/app/k9mail/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt b/app/k9mail/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt
index ebe668d940f..5d8dab8ef78 100644
--- a/app/k9mail/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt
+++ b/app/k9mail/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt
@@ -2,6 +2,7 @@ package com.fsck.k9.backends
 
 import android.content.Context
 import com.fsck.k9.Account
+import com.fsck.k9.BuildConfig
 import com.fsck.k9.backend.BackendFactory
 import com.fsck.k9.backend.api.Backend
 import com.fsck.k9.backend.imap.ImapBackend
@@ -69,6 +70,9 @@ class ImapBackendFactory(
             override fun isSubscribedFoldersOnly() = account.isSubscribedFoldersOnly
 
             override fun useCompression() = account.useCompression
+            override fun clientIdAppName(): String? {
+                return BuildConfig.CLIENT_ID_APP_NAME.takeIf { account.isSendClientIdEnabled }
+            }
         }
     }
 
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java
index 4a9294ecebb..e6bf4e35677 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java
@@ -86,6 +86,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
     private Account mAccount;
     private boolean mMakeDefault;
     private CheckBox useCompressionCheckBox;
+    private CheckBox isSendClientIdEnabledCheckBox;
     private CheckBox mSubscribedFoldersOnly;
     private AuthTypeAdapter mAuthTypeAdapter;
     private ConnectionSecurity[] mConnectionSecurityChoices = ConnectionSecurity.values();
@@ -134,6 +135,7 @@ public void onCreate(Bundle savedInstanceState) {
         mWebdavMailboxPathView = findViewById(R.id.webdav_mailbox_path);
         mNextButton = findViewById(R.id.next);
         useCompressionCheckBox = findViewById(R.id.use_compression);
+        isSendClientIdEnabledCheckBox = findViewById(R.id.is_send_client_id_enabled);
         mSubscribedFoldersOnly = findViewById(R.id.subscribed_folders_only);
         mAllowClientCertificateView = findViewById(R.id.account_allow_client_certificate);
 
@@ -213,6 +215,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 findViewById(R.id.webdav_owa_path_section).setVisibility(View.GONE);
                 findViewById(R.id.webdav_auth_path_section).setVisibility(View.GONE);
                 useCompressionCheckBox.setVisibility(View.GONE);
+                isSendClientIdEnabledCheckBox.setVisibility(View.GONE);
                 mSubscribedFoldersOnly.setVisibility(View.GONE);
             } else if (settings.type.equals(Protocols.IMAP)) {
                 serverLayoutView.setHint(getString(R.string.account_setup_incoming_imap_server_label));
@@ -244,6 +247,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 findViewById(R.id.account_auth_type_label).setVisibility(View.GONE);
                 findViewById(R.id.account_auth_type).setVisibility(View.GONE);
                 useCompressionCheckBox.setVisibility(View.GONE);
+                isSendClientIdEnabledCheckBox.setVisibility(View.GONE);
                 mSubscribedFoldersOnly.setVisibility(View.GONE);
 
                 String path = WebDavStoreSettings.getPath(settings);
@@ -294,6 +298,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
             updateViewFromSecurity();
 
             useCompressionCheckBox.setChecked(mAccount.useCompression());
+            isSendClientIdEnabledCheckBox.setChecked(mAccount.isSendClientIdEnabled());
 
             if (settings.host != null) {
                 mServerView.setText(settings.host);
@@ -623,6 +628,7 @@ protected void onNext() {
             mAccount.setIncomingServerSettings(settings);
 
             mAccount.setUseCompression(useCompressionCheckBox.isChecked());
+            mAccount.setSendClientIdEnabled(isSendClientIdEnabledCheckBox.isChecked());
             mAccount.setSubscribedFoldersOnly(mSubscribedFoldersOnly.isChecked());
 
             AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
diff --git a/app/ui/legacy/src/main/res/layout/account_setup_incoming.xml b/app/ui/legacy/src/main/res/layout/account_setup_incoming.xml
index 3807ce8c82e..c35a1750d0a 100644
--- a/app/ui/legacy/src/main/res/layout/account_setup_incoming.xml
+++ b/app/ui/legacy/src/main/res/layout/account_setup_incoming.xml
@@ -253,6 +253,12 @@
                     android:layout_width="wrap_content"
                     android:text="@string/account_setup_incoming_use_compression" />
 
+            <CheckBox
+                    android:id="@+id/is_send_client_id_enabled"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:text="@string/account_setup_incoming_send_client_id" />
+
             <View
                     android:layout_width="match_parent"
                     android:layout_height="0dip"
diff --git a/app/ui/legacy/src/main/res/values/strings.xml b/app/ui/legacy/src/main/res/values/strings.xml
index 8ac577aaa19..3e175c178e3 100644
--- a/app/ui/legacy/src/main/res/values/strings.xml
+++ b/app/ui/legacy/src/main/res/values/strings.xml
@@ -429,6 +429,7 @@ Please submit bug reports, contribute new features and ask questions at
     <string name="account_setup_incoming_delete_policy_markread_label">Mark as read on server</string>
 
     <string name="account_setup_incoming_use_compression">Use compression</string>
+    <string name="account_setup_incoming_send_client_id">Send client ID</string>
 
     <string name="account_setup_expunge_policy_label">Erase deleted messages on server</string>
     <string name="account_setup_expunge_policy_immediately">Immediately</string>
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.java b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.java
index 3d4b591e00d..bfc22abf0d3 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.java
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.java
@@ -13,6 +13,7 @@ class Capabilities {
     public static final String LOGINDISABLED = "LOGINDISABLED";
     public static final String NAMESPACE = "NAMESPACE";
     public static final String COMPRESS_DEFLATE = "COMPRESS=DEFLATE";
+    public static final String ID = "ID";
     public static final String STARTTLS = "STARTTLS";
     public static final String SPECIAL_USE = "SPECIAL-USE";
     public static final String UID_PLUS = "UIDPLUS";
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapSettings.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapSettings.kt
index 03e7582b5b1..6e44e307245 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapSettings.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapSettings.kt
@@ -15,6 +15,7 @@ internal interface ImapSettings {
     val password: String?
     val clientCertificateAlias: String?
     val useCompression: Boolean
+    val clientIdAppName: String?
 
     var pathPrefix: String?
     var pathDelimiter: String?
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt
index 0bfbd58eeb1..295ba4536d8 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt
@@ -4,4 +4,5 @@ interface ImapStoreConfig {
     val logLabel: String
     fun isSubscribedFoldersOnly(): Boolean
     fun useCompression(): Boolean
+    fun clientIdAppName(): String?
 }
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapConnection.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapConnection.kt
index 22dc8eb62f0..3a5bf6f9632 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapConnection.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapConnection.kt
@@ -96,6 +96,7 @@ internal class RealImapConnection(
             extractOrRequestCapabilities(responses)
 
             enableCompressionIfRequested()
+            sendClientIdIfSupported()
 
             retrievePathPrefixIfNecessary()
             retrievePathDelimiterIfNecessary()
@@ -561,6 +562,17 @@ internal class RealImapConnection(
         }
     }
 
+    private fun sendClientIdIfSupported() {
+        if (hasCapability(Capabilities.ID) && settings.clientIdAppName != null) {
+            val encodedAppName = ImapUtility.encodeString(settings.clientIdAppName)
+            try {
+                executeSimpleCommand("""ID ("name" $encodedAppName)""")
+            } catch (e: NegativeImapResponseException) {
+                Timber.d(e, "Ignoring negative response to ID command")
+            }
+        }
+    }
+
     private fun enableCompression() {
         try {
             executeSimpleCommand(Commands.COMPRESS_DEFLATE)
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
index b100bb65e9f..54870e66265 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
@@ -299,6 +299,9 @@ internal open class RealImapStore(
         override val useCompression: Boolean
             get() = this@RealImapStore.config.useCompression()
 
+        override val clientIdAppName: String?
+            get() = this@RealImapStore.config.clientIdAppName()
+
         override var pathPrefix: String?
             get() = this@RealImapStore.pathPrefix
             set(value) {


Test patch:
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapConnectionTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapConnectionTest.kt
index d1d236687a8..b56a1ca1f5d 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapConnectionTest.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapConnectionTest.kt
@@ -843,6 +843,100 @@ class RealImapConnectionTest {
         server.verifyInteractionCompleted()
     }
 
+    @Test
+    fun `open() with ID capability and clientIdAppName should send ID command`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
+            expect("""3 ID ("name" "AppName")""")
+            output("""* ID ("name" "CustomImapServer" "vendor" "Company, Inc." "version" "0.1")""")
+            output("3 OK ID completed")
+            simplePostAuthenticationDialog(tag = 4)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
+    @Test
+    fun `open() without ID capability and clientIdAppName set should send not ID command`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog()
+            simplePostAuthenticationDialog(tag = 3)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
+    @Test
+    fun `open() with ID capability but empty clientIdAppName should not send ID command`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
+            simplePostAuthenticationDialog(tag = 3)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = null)
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
+    @Test
+    fun `open() with empty untagged ID response`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
+            expect("""3 ID ("name" "AppName")""")
+            output("""* ID NIL""")
+            output("3 OK ID completed")
+            simplePostAuthenticationDialog(tag = 4)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
+    @Test
+    fun `open() with missing untagged ID response`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
+            expect("""3 ID ("name" "AppName")""")
+            output("3 OK ID completed")
+            simplePostAuthenticationDialog(tag = 4)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
+    @Test
+    fun `open() with BAD response to ID command should not throw`() {
+        val server = MockImapServer().apply {
+            simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
+            expect("""3 ID ("name" "AppName")""")
+            output("3 BAD Server doesn't like the ID command")
+            simplePostAuthenticationDialog(tag = 4)
+        }
+        val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
+
+        imapConnection.open()
+
+        server.verifyConnectionStillOpen()
+        server.verifyInteractionCompleted()
+    }
+
     @Test
     fun `isConnected without previous open() should return false`() {
         val settings = createImapSettings()
@@ -1037,6 +1131,7 @@ class RealImapConnectionTest {
         connectionSecurity: ConnectionSecurity = ConnectionSecurity.NONE,
         authType: AuthType = AuthType.PLAIN,
         useCompression: Boolean = false,
+        clientIdAppName: String? = null,
     ): ImapConnection {
         server.start()
 
@@ -1048,6 +1143,7 @@ class RealImapConnectionTest {
             username = USERNAME,
             password = PASSWORD,
             useCompression = useCompression,
+            clientIdAppName = clientIdAppName,
         )
 
         return createImapConnection(settings, socketFactory, oAuth2TokenProvider)
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
index 2788b147cd9..04c9af4f9a4 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
@@ -392,20 +392,26 @@ class RealImapStoreTest {
     private fun createTestImapStore(
         isSubscribedFoldersOnly: Boolean = false,
         useCompression: Boolean = false,
+        clientIdAppName: String? = null,
     ): TestImapStore {
         return TestImapStore(
             serverSettings = createServerSettings(),
-            config = createImapStoreConfig(isSubscribedFoldersOnly, useCompression),
+            config = createImapStoreConfig(isSubscribedFoldersOnly, useCompression, clientIdAppName),
             trustedSocketFactory = mock(),
             oauth2TokenProvider = null,
         )
     }
 
-    private fun createImapStoreConfig(isSubscribedFoldersOnly: Boolean, useCompression: Boolean): ImapStoreConfig {
+    private fun createImapStoreConfig(
+        isSubscribedFoldersOnly: Boolean,
+        useCompression: Boolean,
+        clientIdAppName: String?,
+    ): ImapStoreConfig {
         return object : ImapStoreConfig {
             override val logLabel: String = "irrelevant"
             override fun isSubscribedFoldersOnly(): Boolean = isSubscribedFoldersOnly
             override fun useCompression(): Boolean = useCompression
+            override fun clientIdAppName(): String? = clientIdAppName
         }
     }
 
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SimpleImapSettings.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SimpleImapSettings.kt
index 4cc68586e69..1e2b2d8d924 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SimpleImapSettings.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SimpleImapSettings.kt
@@ -3,6 +3,7 @@ package com.fsck.k9.mail.store.imap
 import com.fsck.k9.mail.AuthType
 import com.fsck.k9.mail.ConnectionSecurity
 
+@Suppress("LongParameterList")
 internal class SimpleImapSettings(
     override val host: String,
     override val port: Int = 0,
@@ -11,6 +12,7 @@ internal class SimpleImapSettings(
     override val username: String,
     override val password: String? = null,
     override val useCompression: Boolean = false,
+    override val clientIdAppName: String? = null,
 ) : ImapSettings {
     override val clientCertificateAlias: String? = null