diff --git a/FAQ.md b/FAQ.md
index 0d1b2624..88bd97f4 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -99,15 +99,15 @@ This IMAP extension is required to implement two way synchronization, which is n
So, unless your provider can enable this extension, you cannot use FairEmail for this provider.
-**(11) Why is STARTTLS for IMAP not supported?**
+~~**(11) Why is STARTTLS for IMAP not supported?**~~
-STARTTLS starts with a not encrypted connection and is therefore not secure.
-All well known IMAP servers support IMAP with a plain SSL connection, so there is no need to support STARTTLS for IMAP.
-If you encounter an IMAP server that requires STARTTLS, please let me know.
+~~STARTTLS starts with a not encrypted connection and is therefore not secure.~~
+~~All well known IMAP servers support IMAP with a plain SSL connection, so there is no need to support STARTTLS for IMAP.~~
+~~If you encounter an IMAP server that requires STARTTLS, please let me know.~~
-For more background information, please see [this article](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery).
+~~For more background information, please see [this article](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery).~~
-tl;dr; "*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, there’s still no guarantee your communication will be encrypted.*"
+~~tl;dr; "*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, there’s still no guarantee your communication will be encrypted.*"~~
**(13) How does search on server work?**
diff --git a/app/schemas/eu.faircode.email.DB/23.json b/app/schemas/eu.faircode.email.DB/23.json
new file mode 100644
index 00000000..f7e32c97
--- /dev/null
+++ b/app/schemas/eu.faircode.email.DB/23.json
@@ -0,0 +1,1034 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 23,
+ "identityHash": "50f45495091ca49a0bcaa5fc9f827604",
+ "entities": [
+ {
+ "tableName": "identity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyto",
+ "columnName": "replyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "store_sent",
+ "columnName": "store_sent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_identity_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "account",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "store_sent",
+ "columnName": "store_sent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen_until",
+ "columnName": "seen_until",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "folder",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `poll_interval` INTEGER, `after` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "after",
+ "columnName": "after",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display",
+ "columnName": "display",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hide",
+ "columnName": "hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unified",
+ "columnName": "unified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_folder_account_name",
+ "unique": true,
+ "columnNames": [
+ "account",
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
+ },
+ {
+ "name": "index_folder_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_folder_name",
+ "unique": false,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
+ },
+ {
+ "name": "index_folder_type",
+ "unique": false,
+ "columnNames": [
+ "type"
+ ],
+ "createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
+ },
+ {
+ "name": "index_folder_unified",
+ "unique": false,
+ "columnNames": [
+ "unified"
+ ],
+ "createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "message",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `deliveredto` TEXT, `inreplyto` TEXT, `thread` TEXT, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "identity",
+ "columnName": "identity",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replying",
+ "columnName": "replying",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "msgid",
+ "columnName": "msgid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "references",
+ "columnName": "references",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deliveredto",
+ "columnName": "deliveredto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inreplyto",
+ "columnName": "inreplyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thread",
+ "columnName": "thread",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "avatar",
+ "columnName": "avatar",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "from",
+ "columnName": "from",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "to",
+ "columnName": "to",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cc",
+ "columnName": "cc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bcc",
+ "columnName": "bcc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reply",
+ "columnName": "reply",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "headers",
+ "columnName": "headers",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sent",
+ "columnName": "sent",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stored",
+ "columnName": "stored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "flagged",
+ "columnName": "flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_seen",
+ "columnName": "ui_seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_flagged",
+ "columnName": "ui_flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_hide",
+ "columnName": "ui_hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_found",
+ "columnName": "ui_found",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_ignored",
+ "columnName": "ui_ignored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_message_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_message_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_message_identity",
+ "unique": false,
+ "columnNames": [
+ "identity"
+ ],
+ "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
+ },
+ {
+ "name": "index_message_replying",
+ "unique": false,
+ "columnNames": [
+ "replying"
+ ],
+ "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
+ },
+ {
+ "name": "index_message_folder_uid_ui_found",
+ "unique": true,
+ "columnNames": [
+ "folder",
+ "uid",
+ "ui_found"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid_ui_found` ON `${TABLE_NAME}` (`folder`, `uid`, `ui_found`)"
+ },
+ {
+ "name": "index_message_msgid_folder_ui_found",
+ "unique": true,
+ "columnNames": [
+ "msgid",
+ "folder",
+ "ui_found"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `${TABLE_NAME}` (`msgid`, `folder`, `ui_found`)"
+ },
+ {
+ "name": "index_message_thread",
+ "unique": false,
+ "columnNames": [
+ "thread"
+ ],
+ "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
+ },
+ {
+ "name": "index_message_received",
+ "unique": false,
+ "columnNames": [
+ "received"
+ ],
+ "createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
+ },
+ {
+ "name": "index_message_ui_seen",
+ "unique": false,
+ "columnNames": [
+ "ui_seen"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
+ },
+ {
+ "name": "index_message_ui_hide",
+ "unique": false,
+ "columnNames": [
+ "ui_hide"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
+ },
+ {
+ "name": "index_message_ui_found",
+ "unique": false,
+ "columnNames": [
+ "ui_found"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
+ },
+ {
+ "name": "index_message_ui_ignored",
+ "unique": false,
+ "columnNames": [
+ "ui_ignored"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "identity",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "identity"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "replying"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "attachment",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `cid` TEXT, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sequence",
+ "columnName": "sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cid",
+ "columnName": "cid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "progress",
+ "columnName": "progress",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "available",
+ "columnName": "available",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_attachment_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
+ },
+ {
+ "name": "index_attachment_message_sequence",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "sequence"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
+ },
+ {
+ "name": "index_attachment_message_cid",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "cid"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_cid` ON `${TABLE_NAME}` (`message`, `cid`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "operation",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "args",
+ "columnName": "args",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_operation_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_operation_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "answer",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "log",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_log_time",
+ "unique": false,
+ "columnNames": [
+ "time"
+ ],
+ "createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"50f45495091ca49a0bcaa5fc9f827604\")"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java
index 66523fe5..f5998a62 100644
--- a/app/src/main/java/eu/faircode/email/DB.java
+++ b/app/src/main/java/eu/faircode/email/DB.java
@@ -45,7 +45,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
- version = 22,
+ version = 23,
entities = {
EntityIdentity.class,
EntityAccount.class,
@@ -275,6 +275,15 @@ public abstract class DB extends RoomDatabase {
db.execSQL("CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `message` (`msgid`, `folder`, `ui_found`)");
}
})
+ .addMigrations(new Migration(22, 23) {
+ @Override
+ public void migrate(SupportSQLiteDatabase db) {
+ Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
+ db.execSQL("ALTER TABLE `account` ADD COLUMN `starttls` INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE `account` ADD COLUMN `insecure` INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE `identity` ADD COLUMN `insecure` INTEGER NOT NULL DEFAULT 0");
+ }
+ })
.build();
}
diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java
index 9f680d08..4e0881f9 100644
--- a/app/src/main/java/eu/faircode/email/EntityAccount.java
+++ b/app/src/main/java/eu/faircode/email/EntityAccount.java
@@ -41,6 +41,10 @@ public class EntityAccount {
@NonNull
public String host; // IMAP
@NonNull
+ public Boolean starttls;
+ @NonNull
+ public Boolean insecure;
+ @NonNull
public Integer port;
@NonNull
public String user;
diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java
index 8260b49e..60ebc76b 100644
--- a/app/src/main/java/eu/faircode/email/EntityIdentity.java
+++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java
@@ -54,10 +54,12 @@ public class EntityIdentity {
@NonNull
public String host; // SMTP
@NonNull
- public Integer port;
- @NonNull
public Boolean starttls;
@NonNull
+ public Boolean insecure;
+ @NonNull
+ public Integer port;
+ @NonNull
public String user;
@NonNull
public String password;
diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java
index 33104fc9..2f20766c 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAccount.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java
@@ -28,12 +28,14 @@ import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
@@ -93,14 +95,19 @@ public class FragmentAccount extends FragmentEx {
private ViewGroup view;
private Spinner spProvider;
- private EditText etHost;
+
private EditText etDomain;
private Button btnAutoConfig;
+
+ private EditText etHost;
+ private CheckBox cbStartTls;
+ private CheckBox cbInsecure;
private EditText etPort;
- private Button btnAuthorize;
private EditText etUser;
private TextInputLayout tilPassword;
+ private Button btnAuthorize;
+
private Button btnAdvanced;
private TextView tvName;
@@ -155,6 +162,9 @@ public class FragmentAccount extends FragmentEx {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setSubtitle(R.string.title_edit_account);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ final boolean insecure = prefs.getBoolean("insecure", false);
+
view = (ViewGroup) inflater.inflate(R.layout.fragment_account, container, false);
// Get controls
@@ -165,11 +175,13 @@ public class FragmentAccount extends FragmentEx {
etHost = view.findViewById(R.id.etHost);
etPort = view.findViewById(R.id.etPort);
-
- btnAuthorize = view.findViewById(R.id.btnAuthorize);
+ cbStartTls = view.findViewById(R.id.cbStartTls);
+ cbInsecure = view.findViewById(R.id.cbInsecure);
etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword);
+ btnAuthorize = view.findViewById(R.id.btnAuthorize);
+
btnAdvanced = view.findViewById(R.id.btnAdvanced);
etName = view.findViewById(R.id.etName);
@@ -214,6 +226,8 @@ public class FragmentAccount extends FragmentEx {
public void onItemSelected(AdapterView> adapterView, View view, int position, long id) {
Provider provider = (Provider) adapterView.getSelectedItem();
grpServer.setVisibility(position == 1 ? View.VISIBLE : View.GONE);
+ cbStartTls.setVisibility(position == 1 && insecure ? View.VISIBLE : View.GONE);
+ cbInsecure.setVisibility(position == 1 && insecure ? View.VISIBLE : View.GONE);
grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
btnAuthorize.setVisibility(provider.type == null ? View.GONE : View.VISIBLE);
@@ -293,6 +307,13 @@ public class FragmentAccount extends FragmentEx {
}
});
+ cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ etPort.setHint(checked ? "143" : "993");
+ }
+ });
+
tilPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -401,6 +422,8 @@ public class FragmentAccount extends FragmentEx {
Bundle args = new Bundle();
args.putLong("id", id);
args.putString("host", etHost.getText().toString());
+ args.putBoolean("starttls", cbStartTls.isChecked());
+ args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
@@ -411,6 +434,8 @@ public class FragmentAccount extends FragmentEx {
protected CheckResult onLoad(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
String host = args.getString("host");
+ boolean starttls = args.getBoolean("starttls");
+ boolean insecure = args.getBoolean("insecure");
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
@@ -419,22 +444,22 @@ public class FragmentAccount extends FragmentEx {
if (TextUtils.isEmpty(host))
throw new Throwable(getContext().getString(R.string.title_no_host));
if (TextUtils.isEmpty(port))
- port = "993";
+ port = (starttls ? "143" : "993");
if (TextUtils.isEmpty(user))
throw new Throwable(getContext().getString(R.string.title_no_user));
- if (TextUtils.isEmpty(password))
+ if (TextUtils.isEmpty(password) && !insecure)
throw new Throwable(getContext().getString(R.string.title_no_password));
CheckResult result = new CheckResult();
result.folders = new ArrayList<>();
// Check IMAP server / get folders
- Properties props = MessageHelper.getSessionProperties(auth_type);
+ Properties props = MessageHelper.getSessionProperties(auth_type, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
IMAPStore istore = null;
try {
- istore = (IMAPStore) isession.getStore("imaps");
+ istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps");
try {
istore.connect(host, Integer.parseInt(port), user, password);
} catch (AuthenticationFailedException ex) {
@@ -574,6 +599,8 @@ public class FragmentAccount extends FragmentEx {
Bundle args = new Bundle();
args.putLong("id", id);
args.putString("host", etHost.getText().toString());
+ args.putBoolean("starttls", cbStartTls.isChecked());
+ args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
@@ -597,6 +624,8 @@ public class FragmentAccount extends FragmentEx {
@Override
protected Void onLoad(Context context, Bundle args) throws Throwable {
String host = args.getString("host");
+ boolean starttls = args.getBoolean("starttls");
+ boolean insecure = args.getBoolean("insecure");
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
@@ -619,10 +648,10 @@ public class FragmentAccount extends FragmentEx {
if (TextUtils.isEmpty(host))
throw new Throwable(getContext().getString(R.string.title_no_host));
if (TextUtils.isEmpty(port))
- port = "993";
+ port = (starttls ? "143" : "993");
if (TextUtils.isEmpty(user))
throw new Throwable(getContext().getString(R.string.title_no_user));
- if (TextUtils.isEmpty(password))
+ if (TextUtils.isEmpty(password) && !insecure)
throw new Throwable(getContext().getString(R.string.title_no_password));
if (TextUtils.isEmpty(interval))
interval = "9";
@@ -633,11 +662,11 @@ public class FragmentAccount extends FragmentEx {
// Check IMAP server
if (synchronize) {
- Session isession = Session.getInstance(MessageHelper.getSessionProperties(auth_type), null);
+ Session isession = Session.getInstance(MessageHelper.getSessionProperties(auth_type, insecure), null);
isession.setDebug(true);
IMAPStore istore = null;
try {
- istore = (IMAPStore) isession.getStore("imaps");
+ istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps");
try {
istore.connect(host, Integer.parseInt(port), user, password);
} catch (AuthenticationFailedException ex) {
@@ -669,6 +698,8 @@ public class FragmentAccount extends FragmentEx {
account = new EntityAccount();
account.host = host;
+ account.starttls = starttls;
+ account.insecure = insecure;
account.port = Integer.parseInt(port);
account.user = user;
account.password = password;
@@ -828,6 +859,8 @@ public class FragmentAccount extends FragmentEx {
// Initialize
Helper.setViewsEnabled(view, false);
btnAuthorize.setVisibility(View.GONE);
+ cbStartTls.setVisibility(View.GONE);
+ cbInsecure.setVisibility(View.GONE);
tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
btnAdvanced.setVisibility(View.GONE);
diff --git a/app/src/main/java/eu/faircode/email/FragmentFolder.java b/app/src/main/java/eu/faircode/email/FragmentFolder.java
index 7c7e1d64..c39f895f 100644
--- a/app/src/main/java/eu/faircode/email/FragmentFolder.java
+++ b/app/src/main/java/eu/faircode/email/FragmentFolder.java
@@ -135,9 +135,9 @@ public class FragmentFolder extends FragmentEx {
if (folder == null || !folder.name.equals(name)) {
EntityAccount account = db.account().getAccount(folder == null ? aid : folder.account);
- Properties props = MessageHelper.getSessionProperties(account.auth_type);
+ Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
Session isession = Session.getInstance(props, null);
- istore = (IMAPStore) isession.getStore("imaps");
+ istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps");
Helper.connect(context, istore, account);
if (folder == null) {
@@ -239,9 +239,9 @@ public class FragmentFolder extends FragmentEx {
EntityFolder folder = db.folder().getFolder(id);
EntityAccount account = db.account().getAccount(folder.account);
- Properties props = MessageHelper.getSessionProperties(account.auth_type);
+ Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
Session isession = Session.getInstance(props, null);
- istore = (IMAPStore) isession.getStore("imaps");
+ istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps");
Helper.connect(context, istore, account);
IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java
index e7906ea4..49ae40b3 100644
--- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java
+++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java
@@ -21,8 +21,10 @@ package eu.faircode.email;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
+import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -74,6 +76,7 @@ public class FragmentIdentity extends FragmentEx {
private Button btnAutoConfig;
private EditText etHost;
private CheckBox cbStartTls;
+ private CheckBox cbInsecure;
private EditText etPort;
private EditText etUser;
private TextInputLayout tilPassword;
@@ -102,6 +105,9 @@ public class FragmentIdentity extends FragmentEx {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setSubtitle(R.string.title_edit_identity);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ final boolean insecure = prefs.getBoolean("insecure", false);
+
view = (ViewGroup) inflater.inflate(R.layout.fragment_identity, container, false);
// Get controls
@@ -120,8 +126,8 @@ public class FragmentIdentity extends FragmentEx {
etHost = view.findViewById(R.id.etHost);
cbStartTls = view.findViewById(R.id.cbStartTls);
+ cbInsecure = view.findViewById(R.id.cbInsecure);
etPort = view.findViewById(R.id.etPort);
-
etUser = view.findViewById(R.id.etUser);
tilPassword = view.findViewById(R.id.tilPassword);
@@ -191,21 +197,6 @@ public class FragmentIdentity extends FragmentEx {
}
});
- btnAdvanced.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
- grpAdvanced.setVisibility(visibility);
- if (visibility == View.VISIBLE)
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- ((ScrollView) view).smoothScrollTo(0, tvEmail.getTop());
- }
- });
- }
- });
-
spProvider.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> adapterView, View view, int position, long id) {
@@ -275,6 +266,22 @@ public class FragmentIdentity extends FragmentEx {
}
});
+ btnAdvanced.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
+ grpAdvanced.setVisibility(visibility);
+ cbInsecure.setVisibility(insecure ? visibility : View.GONE);
+ if (visibility == View.VISIBLE)
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ ((ScrollView) view).smoothScrollTo(0, tvEmail.getTop());
+ }
+ });
+ }
+ });
+
cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -307,6 +314,7 @@ public class FragmentIdentity extends FragmentEx {
args.putInt("auth_type", account == null || account.auth_type == null ? Helper.AUTH_TYPE_PASSWORD : account.auth_type);
args.putString("host", etHost.getText().toString());
args.putBoolean("starttls", cbStartTls.isChecked());
+ args.putBoolean("insecure", cbInsecure.isChecked());
args.putString("port", etPort.getText().toString());
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
@@ -324,6 +332,7 @@ public class FragmentIdentity extends FragmentEx {
String replyto = args.getString("replyto");
String host = args.getString("host");
boolean starttls = args.getBoolean("starttls");
+ boolean insecure = args.getBoolean("insecure");
String port = args.getString("port");
String user = args.getString("user");
String password = args.getString("password");
@@ -339,10 +348,10 @@ public class FragmentIdentity extends FragmentEx {
if (TextUtils.isEmpty(host))
throw new IllegalArgumentException(getContext().getString(R.string.title_no_host));
if (TextUtils.isEmpty(port))
- port = "465";
+ port = (starttls ? "587" : "465");
if (TextUtils.isEmpty(user))
throw new IllegalArgumentException(getContext().getString(R.string.title_no_user));
- if (TextUtils.isEmpty(password))
+ if (TextUtils.isEmpty(password) && !insecure)
throw new IllegalArgumentException(getContext().getString(R.string.title_no_password));
if (TextUtils.isEmpty(replyto))
@@ -350,7 +359,7 @@ public class FragmentIdentity extends FragmentEx {
// Check SMTP server
if (synchronize) {
- Properties props = MessageHelper.getSessionProperties(auth_type);
+ Properties props = MessageHelper.getSessionProperties(auth_type, insecure);
Session isession = Session.getInstance(props, null);
isession.setDebug(true);
Transport itransport = isession.getTransport(starttls ? "smtp" : "smtps");
@@ -382,8 +391,9 @@ public class FragmentIdentity extends FragmentEx {
identity.email = email;
identity.replyto = replyto;
identity.host = host;
- identity.port = Integer.parseInt(port);
identity.starttls = starttls;
+ identity.insecure = insecure;
+ identity.port = Integer.parseInt(port);
identity.user = user;
identity.password = password;
identity.auth_type = auth_type;
@@ -476,6 +486,7 @@ public class FragmentIdentity extends FragmentEx {
// Initialize
Helper.setViewsEnabled(view, false);
+ cbInsecure.setVisibility(View.GONE);
tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
btnSave.setVisibility(View.GONE);
btnAdvanced.setVisibility(View.GONE);
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java
index eec2b512..8e53dfa6 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptions.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java
@@ -38,6 +38,7 @@ public class FragmentOptions extends FragmentEx {
private CheckBox cbBrowse;
private CheckBox cbSwipe;
private CheckBox cbCompact;
+ private CheckBox cbInsecure;
private CheckBox cbDebug;
@Override
@@ -54,6 +55,7 @@ public class FragmentOptions extends FragmentEx {
cbBrowse = view.findViewById(R.id.cbBrowse);
cbSwipe = view.findViewById(R.id.cbSwipe);
cbCompact = view.findViewById(R.id.cbCompact);
+ cbInsecure = view.findViewById(R.id.cbInsecure);
cbDebug = view.findViewById(R.id.cbDebug);
// Wire controls
@@ -112,6 +114,14 @@ public class FragmentOptions extends FragmentEx {
}
});
+ cbInsecure.setChecked(prefs.getBoolean("insecure", false));
+ cbInsecure.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ prefs.edit().putBoolean("insecure", checked).apply();
+ }
+ });
+
cbDebug.setChecked(prefs.getBoolean("debug", false));
cbDebug.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java
index be6c0133..9105a31f 100644
--- a/app/src/main/java/eu/faircode/email/MessageHelper.java
+++ b/app/src/main/java/eu/faircode/email/MessageHelper.java
@@ -63,11 +63,13 @@ public class MessageHelper {
final static int NETWORK_TIMEOUT = 60 * 1000; // milliseconds
- static Properties getSessionProperties(int auth_type) {
+ static Properties getSessionProperties(int auth_type, boolean insecure) {
Properties props = new Properties();
+ String checkserveridentity = Boolean.toString(!insecure).toLowerCase();
+
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
- props.put("mail.imaps.ssl.checkserveridentity", "true");
+ props.put("mail.imaps.ssl.checkserveridentity", checkserveridentity);
props.put("mail.imaps.ssl.trust", "*");
props.put("mail.imaps.starttls.enable", "false");
@@ -79,22 +81,35 @@ public class MessageHelper {
props.put("mail.imaps.connectionpool.debug", "true");
props.put("mail.imaps.connectionpooltimeout", Integer.toString(3 * 60 * 1000)); // default: 45 sec
- // "mail.imaps.finalizecleanclose"
-
// https://tools.ietf.org/html/rfc4978
// https://docs.oracle.com/javase/8/docs/api/java/util/zip/Deflater.html
- if (true) {
- Log.i(Helper.TAG, "IMAP compress enabled");
- props.put("mail.imaps.compress.enable", "true");
- //props.put("mail.imaps.compress.level", "-1");
- //props.put("mail.imaps.compress.strategy", "0");
- }
+ props.put("mail.imaps.compress.enable", "true");
+ //props.put("mail.imaps.compress.level", "-1");
+ //props.put("mail.imaps.compress.strategy", "0");
props.put("mail.imaps.fetchsize", Integer.toString(48 * 1024)); // default 16K
props.put("mail.imaps.peek", "true");
+ props.put("mail.imap.ssl.checkserveridentity", checkserveridentity);
+ props.put("mail.imap.ssl.trust", "*");
+ props.put("mail.imap.starttls.enable", "true");
+ props.put("mail.imap.starttls.required", "true");
+ props.put("mail.imap.auth", "true");
+
+ props.put("mail.imap.connectiontimeout", Integer.toString(NETWORK_TIMEOUT));
+ props.put("mail.imap.timeout", Integer.toString(NETWORK_TIMEOUT));
+ props.put("mail.imap.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead
+
+ props.put("mail.imap.connectionpool.debug", "true");
+ props.put("mail.imap.connectionpooltimeout", Integer.toString(3 * 60 * 1000)); // default: 45 sec
+
+ props.put("mail.imap.compress.enable", "true");
+
+ props.put("mail.imap.fetchsize", Integer.toString(48 * 1024)); // default 16K
+ props.put("mail.imap.peek", "true");
+
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
- props.put("mail.smtps.ssl.checkserveridentity", "true");
+ props.put("mail.smtps.ssl.checkserveridentity", checkserveridentity);
props.put("mail.smtps.ssl.trust", "*");
props.put("mail.smtps.starttls.enable", "false");
props.put("mail.smtps.starttls.required", "false");
@@ -104,7 +119,7 @@ public class MessageHelper {
props.put("mail.smtps.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead
props.put("mail.smtps.timeout", Integer.toString(NETWORK_TIMEOUT));
- props.put("mail.smtp.ssl.checkserveridentity", "true");
+ props.put("mail.smtp.ssl.checkserveridentity", checkserveridentity);
props.put("mail.smtp.ssl.trust", "*");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
@@ -145,6 +160,7 @@ public class MessageHelper {
Log.i(Helper.TAG, "Auth type=" + auth_type);
if (auth_type == Helper.AUTH_TYPE_GMAIL) {
props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
+ props.put("mail.imap.auth.mechanisms", "XOAUTH2");
props.put("mail.smtps.auth.mechanisms", "XOAUTH2");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
}
diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
index 2ba4cc0b..647c6be8 100644
--- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
+++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
@@ -574,12 +574,12 @@ public class ServiceSynchronize extends LifecycleService {
System.setProperty("mail.socket.debug", Boolean.toString(debug));
// Create session
- Properties props = MessageHelper.getSessionProperties(account.auth_type);
+ Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
final Session isession = Session.getInstance(props, null);
isession.setDebug(debug);
// adb -t 1 logcat | grep "fairemail\|System.out"
- final IMAPStore istore = (IMAPStore) isession.getStore("imaps");
+ final IMAPStore istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps");
final Map folders = new HashMap<>();
List syncs = new ArrayList<>();
List idlers = new ArrayList<>();
@@ -1331,7 +1331,7 @@ public class ServiceSynchronize extends LifecycleService {
}
// Create session
- Properties props = MessageHelper.getSessionProperties(ident.auth_type);
+ Properties props = MessageHelper.getSessionProperties(ident.auth_type, ident.insecure);
final Session isession = Session.getInstance(props, null);
// Create message
diff --git a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java
index c0673230..b18d8610 100644
--- a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java
+++ b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java
@@ -68,12 +68,12 @@ public class ViewModelBrowse extends ViewModel {
EntityAccount account = db.account().getAccount(folder.account);
if (imessages == null) {
- Properties props = MessageHelper.getSessionProperties(account.auth_type);
+ Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure);
props.setProperty("mail.imap.throwsearchexception", "true");
Session isession = Session.getInstance(props, null);
Log.i(Helper.TAG, "Boundary connecting account=" + account.name);
- istore = (IMAPStore) isession.getStore("imaps");
+ istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps");
Helper.connect(context, istore, account);
Log.i(Helper.TAG, "Boundary opening folder=" + folder.name);
diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml
index e9b501f4..c95e3733 100644
--- a/app/src/main/res/layout/fragment_account.xml
+++ b/app/src/main/res/layout/fragment_account.xml
@@ -58,8 +58,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:minHeight="0dp"
android:minWidth="0dp"
+ android:minHeight="0dp"
android:text="@string/title_autoconfig"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etDomain" />
@@ -118,6 +118,24 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvHost" />
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
@@ -207,8 +225,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:minHeight="0dp"
android:minWidth="0dp"
+ android:minHeight="0dp"
android:text="@string/title_setup_advanced"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
@@ -241,8 +259,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:minHeight="0dp"
android:minWidth="0dp"
+ android:minHeight="0dp"
android:text="@string/title_account_color"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etName" />
diff --git a/app/src/main/res/layout/fragment_identity.xml b/app/src/main/res/layout/fragment_identity.xml
index 334cace4..9643c514 100644
--- a/app/src/main/res/layout/fragment_identity.xml
+++ b/app/src/main/res/layout/fragment_identity.xml
@@ -214,6 +214,15 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etHost" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbInsecure" />
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 17634e9b..b7d7d680 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -106,6 +106,7 @@
Custom
Host name
STARTTLS
+ Allow insecure connections
Port number
User name
Password