mirror of https://github.com/M66B/FairEmail.git
parent
9ff3718561
commit
bc30dee6a1
5
FAQ.md
5
FAQ.md
|
@ -10,9 +10,10 @@ Frequently Asked Questions
|
|||
* Full network access (INTERNET): to send and receive email
|
||||
* View network connections (ACCESS_NETWORK_STATE): to monitor internet connectivity changes
|
||||
* Run at startup (RECEIVE_BOOT_COMPLETED): to start monitoring on device start
|
||||
* In-app billing (BILLING): to allow in-app purchases
|
||||
* Foreground service (FOREGROUND_SERVICE): to run a foreground service on Android 9 Pie and later, see also the next question
|
||||
* Optional: read your contacts (READ_CONTACTS): to autocomplete addresses
|
||||
* ... (BILLING): to offer in-app purchases
|
||||
* ... (FOREGROUND_SERVICE): to run a foreground service on Android 9 Pie and later, see also the next question
|
||||
* Optional: find accounts on the device (GET_ACCOUNTS): to use [OAuth](https://en.wikipedia.org/wiki/OAuth) instead of passwords
|
||||
|
||||
<a name="FAQ2"></a>
|
||||
**(2) Why is there a permanent notification shown?**
|
||||
|
|
|
@ -0,0 +1,844 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "df70f524a0c744041e89eea7e7052e73",
|
||||
"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, `port` INTEGER NOT NULL, `starttls` 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": "port",
|
||||
"columnName": "port",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starttls",
|
||||
"columnName": "starttls",
|
||||
"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, `host` TEXT 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, `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": "host",
|
||||
"columnName": "host",
|
||||
"affinity": "TEXT",
|
||||
"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": "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, `after` 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": "after",
|
||||
"columnName": "after",
|
||||
"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`)"
|
||||
}
|
||||
],
|
||||
"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, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` 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": "inreplyto",
|
||||
"columnName": "inreplyto",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "thread",
|
||||
"columnName": "thread",
|
||||
"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": "subject",
|
||||
"columnName": "subject",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"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": "ui_seen",
|
||||
"columnName": "ui_seen",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ui_hide",
|
||||
"columnName": "ui_hide",
|
||||
"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",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"folder",
|
||||
"uid"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
|
||||
},
|
||||
{
|
||||
"name": "index_message_msgid_folder",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"msgid",
|
||||
"folder"
|
||||
],
|
||||
"createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
|
||||
},
|
||||
{
|
||||
"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`)"
|
||||
}
|
||||
],
|
||||
"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, `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": "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`)"
|
||||
}
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
],
|
||||
"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, \"df70f524a0c744041e89eea7e7052e73\")"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|||
public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
boolean hasAccount;
|
||||
|
||||
static final int REQUEST_PERMISSION = 1;
|
||||
static final int REQUEST_CHOOSE_ACCOUNT = 2;
|
||||
|
||||
static final String ACTION_EDIT_ACCOUNT = BuildConfig.APPLICATION_ID + ".EDIT_ACCOUNT";
|
||||
static final String ACTION_EDIT_IDENTITY = BuildConfig.APPLICATION_ID + ".EDIT_IDENTITY";
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 4,
|
||||
version = 5,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -135,6 +135,14 @@ public abstract class DB extends RoomDatabase {
|
|||
db.execSQL("CREATE TABLE IF NOT EXISTS `answer` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(4, 5) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `account` ADD COLUMN `auth_type` INTEGER NOT NULL DEFAULT 1");
|
||||
db.execSQL("ALTER TABLE `identity` ADD COLUMN `auth_type` INTEGER NOT NULL DEFAULT 1");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ public class EntityAccount {
|
|||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
|
|
@ -59,6 +59,8 @@ public class EntityIdentity {
|
|||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
|
|
@ -19,8 +19,17 @@ package eu.faircode.email;
|
|||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.Html;
|
||||
|
@ -53,6 +62,7 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.MessagingException;
|
||||
|
@ -62,14 +72,18 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import static android.accounts.AccountManager.newChooseAccountIntent;
|
||||
|
||||
public class FragmentAccount extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private EditText etName;
|
||||
private Spinner spProvider;
|
||||
private EditText etHost;
|
||||
private EditText etPort;
|
||||
private Button btnAuthorize;
|
||||
private EditText etUser;
|
||||
private TextInputLayout tilPassword;
|
||||
private TextView tvLink;
|
||||
|
@ -91,6 +105,18 @@ public class FragmentAccount extends FragmentEx {
|
|||
private Group grpInstructions;
|
||||
private Group grpFolders;
|
||||
|
||||
private long id = -1;
|
||||
private int auth_type = Helper.AUTH_TYPE_PASSWORD;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
id = (args == null ? -1 : args.getLong("id", -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
@ -98,14 +124,11 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
view = (ViewGroup) inflater.inflate(R.layout.fragment_account, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = (args == null ? -1 : args.getLong("id", -1));
|
||||
|
||||
// Get controls
|
||||
spProvider = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
btnAuthorize = view.findViewById(R.id.btnAuthorize);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
tilPassword = view.findViewById(R.id.tilPassword);
|
||||
|
@ -143,12 +166,23 @@ public class FragmentAccount extends FragmentEx {
|
|||
tvLink.setText(Html.fromHtml("<a href=\"" + provider.link + "\">" + provider.link + "</a>"));
|
||||
grpInstructions.setVisibility(provider.link == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (provider.imap_port != 0) {
|
||||
if (provider.imap_port > 0) {
|
||||
etName.setText(provider.name);
|
||||
etHost.setText(provider.imap_host);
|
||||
etPort.setText(Integer.toString(provider.imap_port));
|
||||
etUser.requestFocus();
|
||||
if (provider.type == null)
|
||||
etUser.requestFocus();
|
||||
}
|
||||
|
||||
if (provider.type == null) {
|
||||
btnAuthorize.setVisibility(View.GONE);
|
||||
if (auth_type != Helper.AUTH_TYPE_PASSWORD) {
|
||||
auth_type = Helper.AUTH_TYPE_PASSWORD;
|
||||
etUser.setText(null);
|
||||
tilPassword.getEditText().setText(null);
|
||||
}
|
||||
} else
|
||||
btnAuthorize.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -156,6 +190,19 @@ public class FragmentAccount extends FragmentEx {
|
|||
}
|
||||
});
|
||||
|
||||
btnAuthorize.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String permission = Manifest.permission.GET_ACCOUNTS;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O &&
|
||||
ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i(Helper.TAG, "Requesting " + permission);
|
||||
requestPermissions(new String[]{permission}, ActivitySetup.REQUEST_PERMISSION);
|
||||
} else
|
||||
selectAccount();
|
||||
}
|
||||
});
|
||||
|
||||
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
|
@ -181,6 +228,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putInt("auth_type", auth_type);
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
|
||||
|
@ -192,6 +240,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
String password = args.getString("password");
|
||||
int auth_type = args.getInt("auth_type");
|
||||
|
||||
if (TextUtils.isEmpty(host))
|
||||
throw new Throwable(getContext().getString(R.string.title_no_host));
|
||||
|
@ -204,7 +253,10 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
// Check IMAP server / get folders
|
||||
List<EntityFolder> folders = new ArrayList<>();
|
||||
Session isession = Session.getInstance(MessageHelper.getSessionProperties(), null);
|
||||
Properties props = MessageHelper.getSessionProperties(auth_type);
|
||||
|
||||
Session isession = Session.getInstance(props, null);
|
||||
isession.setDebug(true);
|
||||
IMAPStore istore = null;
|
||||
try {
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
|
@ -379,6 +431,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putInt("auth_type", auth_type);
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putString("poll_interval", etInterval.getText().toString());
|
||||
|
@ -396,6 +449,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
String password = args.getString("password");
|
||||
int auth_type = args.getInt("auth_type");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
boolean primary = args.getBoolean("primary");
|
||||
String poll_interval = args.getString("poll_interval");
|
||||
|
@ -421,7 +475,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
// Check IMAP server
|
||||
if (synchronize) {
|
||||
Session isession = Session.getInstance(MessageHelper.getSessionProperties(), null);
|
||||
Session isession = Session.getInstance(MessageHelper.getSessionProperties(auth_type), null);
|
||||
IMAPStore istore = null;
|
||||
try {
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
|
@ -451,6 +505,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
account.port = Integer.parseInt(port);
|
||||
account.user = user;
|
||||
account.password = password;
|
||||
account.auth_type = auth_type;
|
||||
account.synchronize = synchronize;
|
||||
account.primary = (account.synchronize && primary);
|
||||
account.store_sent = false;
|
||||
|
@ -582,6 +637,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
// Initialize
|
||||
Helper.setViewsEnabled(view, false);
|
||||
btnAuthorize.setVisibility(View.GONE);
|
||||
tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
|
||||
tvLink.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
btnCheck.setEnabled(false);
|
||||
|
@ -599,6 +655,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt("provider", spProvider.getSelectedItemPosition());
|
||||
outState.putInt("auth_type", auth_type);
|
||||
outState.putString("password", tilPassword.getEditText().getText().toString());
|
||||
outState.putInt("instructions", grpInstructions.getVisibility());
|
||||
}
|
||||
|
@ -607,10 +664,6 @@ public class FragmentAccount extends FragmentEx {
|
|||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
long id = (args == null ? -1 : args.getLong("id", -1));
|
||||
|
||||
// Observe
|
||||
DB.getInstance(getContext()).account().liveAccount(id).observe(getViewLifecycleOwner(), new Observer<EntityAccount>() {
|
||||
boolean once = false;
|
||||
|
@ -640,6 +693,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
etInterval.setText(account == null ? "9" : Integer.toString(account.poll_interval));
|
||||
} else {
|
||||
int provider = savedInstanceState.getInt("provider");
|
||||
auth_type = savedInstanceState.getInt("auth_type");
|
||||
spProvider.setTag(provider);
|
||||
spProvider.setSelection(provider);
|
||||
tilPassword.getEditText().setText(savedInstanceState.getString("password"));
|
||||
|
@ -648,6 +702,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
|
||||
Helper.setViewsEnabled(view, true);
|
||||
|
||||
btnAuthorize.setVisibility(auth_type == Helper.AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE);
|
||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||
|
||||
btnCheck.setVisibility(cbSynchronize.isChecked() ? View.VISIBLE : View.GONE);
|
||||
|
@ -660,4 +715,70 @@ public class FragmentAccount extends FragmentEx {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void selectAccount() {
|
||||
Log.i(Helper.TAG, "Select account");
|
||||
Provider provider = (Provider) spProvider.getSelectedItem();
|
||||
if (provider.type != null)
|
||||
startActivityForResult(newChooseAccountIntent(
|
||||
null,
|
||||
null,
|
||||
new String[]{provider.type},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null), ActivitySetup.REQUEST_CHOOSE_ACCOUNT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (requestCode == ActivitySetup.REQUEST_PERMISSION)
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
|
||||
selectAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.i(Helper.TAG, "Activity result request=" + requestCode + " result=" + resultCode + " data=" + data);
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
if (requestCode == ActivitySetup.REQUEST_CHOOSE_ACCOUNT) {
|
||||
String name = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
|
||||
String type = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
|
||||
|
||||
String authTokenType = null;
|
||||
if ("com.google".equals(type))
|
||||
authTokenType = "oauth2:https://mail.google.com/";
|
||||
|
||||
AccountManager am = AccountManager.get(getContext());
|
||||
Account[] accounts = am.getAccountsByType(type);
|
||||
Log.i(Helper.TAG, "Accounts=" + accounts.length);
|
||||
for (final Account account : accounts)
|
||||
if (name.equals(account.name)) {
|
||||
am.getAuthToken(
|
||||
account,
|
||||
authTokenType,
|
||||
new Bundle(),
|
||||
getActivity(),
|
||||
new AccountManagerCallback<Bundle>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
try {
|
||||
Bundle bundle = future.getResult();
|
||||
String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
|
||||
Log.i(Helper.TAG, "Got token");
|
||||
|
||||
auth_type = Helper.AUTH_TYPE_GMAIL;
|
||||
etUser.setText(account.name);
|
||||
tilPassword.getEditText().setText(token);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
},
|
||||
null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -804,7 +804,7 @@ public class FragmentCompose extends FragmentEx {
|
|||
text = db.answer().getAnswer(answer).text;
|
||||
draft.subject = context.getString(R.string.title_subject_reply, ref.subject);
|
||||
body = String.format("%s<br><br>%s %s:<br><br>%s",
|
||||
Html.escapeHtml(text).replaceAll("\\r?\\n", "<br />"),
|
||||
text.replaceAll("\\r?\\n", "<br />"),
|
||||
Html.escapeHtml(new Date().toString()),
|
||||
Html.escapeHtml(MessageHelper.getFormattedAddresses(draft.to, true)),
|
||||
HtmlHelper.sanitize(context, ref.read(context), true));
|
||||
|
|
|
@ -79,6 +79,17 @@ public class FragmentIdentity extends FragmentEx {
|
|||
private ProgressBar pbWait;
|
||||
private Group grpInstructions;
|
||||
|
||||
private long id = -1;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
id = (args == null ? -1 : args.getLong("id", -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
@ -86,10 +97,6 @@ public class FragmentIdentity extends FragmentEx {
|
|||
|
||||
view = (ViewGroup) inflater.inflate(R.layout.fragment_identity, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = (args == null ? -1 : args.getLong("id", -1));
|
||||
|
||||
// Get controls
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etEmail = view.findViewById(R.id.etEmail);
|
||||
|
@ -214,6 +221,7 @@ public class FragmentIdentity extends FragmentEx {
|
|||
args.putString("email", etEmail.getText().toString());
|
||||
args.putString("replyto", etReplyTo.getText().toString());
|
||||
args.putLong("account", account == null ? -1 : account.id);
|
||||
args.putInt("auth_type", account == null ? Helper.AUTH_TYPE_PASSWORD : account.auth_type);
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putBoolean("starttls", cbStartTls.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
|
@ -236,6 +244,7 @@ public class FragmentIdentity extends FragmentEx {
|
|||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
String password = args.getString("password");
|
||||
int auth_type = args.getInt("auth_type");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
boolean primary = args.getBoolean("primary");
|
||||
boolean store_sent = args.getBoolean("store_sent");
|
||||
|
@ -260,7 +269,7 @@ public class FragmentIdentity extends FragmentEx {
|
|||
|
||||
// Check SMTP server
|
||||
if (synchronize) {
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Properties props = MessageHelper.getSessionProperties(auth_type);
|
||||
Session isession = Session.getInstance(props, null);
|
||||
Transport itransport = isession.getTransport(starttls ? "smtp" : "smtps");
|
||||
try {
|
||||
|
@ -287,6 +296,7 @@ public class FragmentIdentity extends FragmentEx {
|
|||
identity.starttls = starttls;
|
||||
identity.user = user;
|
||||
identity.password = password;
|
||||
identity.auth_type = auth_type;
|
||||
identity.synchronize = synchronize;
|
||||
identity.primary = (identity.synchronize && primary);
|
||||
identity.store_sent = store_sent;
|
||||
|
@ -394,10 +404,6 @@ public class FragmentIdentity extends FragmentEx {
|
|||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
long id = (args == null ? -1 : args.getLong("id", -1));
|
||||
|
||||
final DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe identity
|
||||
|
@ -447,7 +453,7 @@ public class FragmentIdentity extends FragmentEx {
|
|||
|
||||
EntityAccount unselected = new EntityAccount();
|
||||
unselected.id = -1L;
|
||||
unselected.name = "";
|
||||
unselected.name = getString(R.string.title_select);
|
||||
unselected.primary = false;
|
||||
accounts.add(0, unselected);
|
||||
|
||||
|
|
|
@ -52,6 +52,9 @@ import javax.mail.internet.InternetAddress;
|
|||
public class Helper {
|
||||
static final String TAG = "fairemail";
|
||||
|
||||
static final int AUTH_TYPE_PASSWORD = 1;
|
||||
static final int AUTH_TYPE_GMAIL = 2;
|
||||
|
||||
static int resolveColor(Context context, int attr) {
|
||||
int[] attrs = new int[]{attr};
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs);
|
||||
|
|
|
@ -57,7 +57,7 @@ public class MessageHelper {
|
|||
private MimeMessage imessage;
|
||||
private String raw = null;
|
||||
|
||||
static Properties getSessionProperties() {
|
||||
static Properties getSessionProperties(int auth_type) {
|
||||
Properties props = new Properties();
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
|
||||
|
@ -97,6 +97,12 @@ public class MessageHelper {
|
|||
props.put("mail.mime.address.strict", "false");
|
||||
props.put("mail.mime.decodetext.strict", "false");
|
||||
|
||||
// https://javaee.github.io/javamail/OAuth2
|
||||
if (auth_type == Helper.AUTH_TYPE_GMAIL) {
|
||||
props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
|
||||
props.put("mail.smtps.auth.mechanisms", "XOAUTH2");
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import java.util.Locale;
|
|||
public class Provider {
|
||||
public String name;
|
||||
public String link;
|
||||
public String type;
|
||||
public String imap_host;
|
||||
public int imap_port;
|
||||
public String smtp_host;
|
||||
|
@ -62,6 +63,7 @@ public class Provider {
|
|||
provider = new Provider();
|
||||
provider.name = xml.getAttributeValue(null, "name");
|
||||
provider.link = xml.getAttributeValue(null, "link");
|
||||
provider.type = xml.getAttributeValue(null, "type");
|
||||
} else if ("imap".equals(xml.getName())) {
|
||||
provider.imap_host = xml.getAttributeValue(null, "host");
|
||||
provider.imap_port = xml.getAttributeIntValue(null, "port", 0);
|
||||
|
|
|
@ -117,7 +117,7 @@ public class SearchDataSource extends PositionalDataSource<TupleMessageEx> imple
|
|||
folder = db.folder().getFolder(fid);
|
||||
account = db.account().getAccount(folder.account);
|
||||
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Properties props = MessageHelper.getSessionProperties(account.auth_type);
|
||||
Session isession = Session.getInstance(props, null);
|
||||
|
||||
Log.i(Helper.TAG, "SDS connecting account=" + account.name);
|
||||
|
|
|
@ -73,6 +73,7 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.AuthenticationFailedException;
|
||||
import javax.mail.FetchProfile;
|
||||
import javax.mail.Flags;
|
||||
import javax.mail.Folder;
|
||||
|
@ -390,7 +391,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
if (debug)
|
||||
System.setProperty("mail.socket.debug", "true");
|
||||
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Properties props = MessageHelper.getSessionProperties(account.auth_type);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
isession.setDebug(debug);
|
||||
// adb -t 1 logcat | grep "fairemail\|System.out"
|
||||
|
@ -455,6 +456,8 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
|
||||
// Initiate connection
|
||||
Log.i(Helper.TAG, account.name + " connect");
|
||||
for (EntityFolder folder : db.folder().getFolders(account.id))
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
db.account().setAccountState(account.id, "connecting");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
|
@ -470,9 +473,6 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
throw new IllegalStateException("synchronize folders", ex);
|
||||
}
|
||||
|
||||
for (EntityFolder folder : db.folder().getFolders(account.id))
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
|
||||
// Synchronize folders
|
||||
for (final EntityFolder folder : db.folder().getFolders(account.id, true)) {
|
||||
Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
|
||||
|
@ -752,6 +752,9 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
reportError(account.name, null, ex);
|
||||
|
||||
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
|
||||
|
||||
if (ex instanceof AuthenticationFailedException)
|
||||
break;
|
||||
} finally {
|
||||
// Close store
|
||||
Log.i(Helper.TAG, account.name + " closing");
|
||||
|
@ -840,7 +843,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
doDelete(folder, ifolder, message, jargs, db);
|
||||
|
||||
else if (EntityOperation.SEND.equals(op.name))
|
||||
doSend(isession, message, db);
|
||||
doSend(message, db);
|
||||
|
||||
else if (EntityOperation.ATTACHMENT.equals(op.name))
|
||||
doAttachment(folder, op, ifolder, message, jargs, db);
|
||||
|
@ -960,23 +963,27 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
db.message().deleteMessage(message.id);
|
||||
}
|
||||
|
||||
private void doSend(Session isession, EntityMessage message, DB db) throws MessagingException, IOException {
|
||||
private void doSend(EntityMessage message, DB db) throws MessagingException, IOException {
|
||||
// Send message
|
||||
EntityIdentity ident = db.identity().getIdentity(message.identity);
|
||||
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
|
||||
if (!ident.synchronize) {
|
||||
// Message will remain in outbox
|
||||
return;
|
||||
}
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties(ident.auth_type);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
|
||||
// Create message
|
||||
MimeMessage imessage;
|
||||
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
|
||||
if (reply == null)
|
||||
imessage = MessageHelper.from(this, message, attachments, isession);
|
||||
else
|
||||
imessage = MessageHelper.from(this, message, reply, attachments, isession);
|
||||
|
||||
if (ident.replyto != null)
|
||||
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});
|
||||
|
||||
|
@ -1564,16 +1571,12 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
public void onReceive(Context context, Intent intent) {
|
||||
Log.v(Helper.TAG, outbox.name + " run operations");
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Log.i(Helper.TAG, outbox.name + " start operations");
|
||||
processOperations(outbox, isession, null, null);
|
||||
processOperations(outbox, null, null, null);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex));
|
||||
reportError(null, outbox.name, ex);
|
||||
|
|
|
@ -108,6 +108,15 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvPort" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAuthorize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_authorize"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etPort" />
|
||||
|
||||
<!-- user -->
|
||||
|
||||
<TextView
|
||||
|
@ -118,7 +127,7 @@
|
|||
android:text="@string/title_user"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etPort" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnAuthorize" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etUser"
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
<string name="title_advanced_customtabs">Instead of <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tabs</a></string>
|
||||
<string name="title_advanced_debug">Debug</string>
|
||||
|
||||
<string name="title_select">Select …</string>
|
||||
<string name="title_identity_name">Your name</string>
|
||||
<string name="title_identity_email">Your email address</string>
|
||||
<string name="title_identity_reply_to">Reply to address</string>
|
||||
|
@ -86,7 +87,7 @@
|
|||
<string name="title_port">Port number</string>
|
||||
<string name="title_user">User name</string>
|
||||
<string name="title_password">Password</string>
|
||||
<string name="title_authorize">Authorize</string>
|
||||
<string name="title_authorize">Select account</string>
|
||||
<string name="title_instructions">Instructions</string>
|
||||
<string name="title_store_sent">Store sent messages (enable if needed only)</string>
|
||||
<string name="title_interval">Poll/keep-alive interval (minutes)</string>
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
<providers>
|
||||
<provider
|
||||
name="Gmail"
|
||||
link="https://support.google.com/mail/answer/7126229">
|
||||
link="https://support.google.com/mail/answer/7126229"
|
||||
type="com.google">
|
||||
<imap
|
||||
host="imap.gmail.com"
|
||||
port="993" />
|
||||
|
|
Loading…
Reference in New Issue