diff --git a/FAQ.md b/FAQ.md
index e0bcd6a491..e8fe7e2998 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -28,7 +28,6 @@ Please see [here](#user-content-faq22) for common error messages.
Related questions:
-* [Why is POP not supported?](#user-content-faq11)
* [Why is ActiveSync not supported?](#user-content-faq133)
* [Why is OAuth not supported?](#user-content-faq111)
@@ -111,7 +110,7 @@ FairEmail follows all the best practices for an email client as decribed in [thi
* [(7) Why are sent messages not appearing (directly) in the sent folder?](#user-content-faq7)
* [(8) Can I use a Microsoft Exchange account?](#user-content-faq8)
* [(9) What are identities / how do I add an alias?](#user-content-faq9)
-* [(11) Why is POP not supported?](#user-content-faq11)
+* [~~(11) Why is POP not supported?~~](#user-content-faq11)
* [~~(10) What does 'UIDPLUS not supported' mean?~~](#user-content-faq10)
* [(12) How does encryption/decryption work?](#user-content-faq12)
* [(13) How does search on device/server work?](#user-content-faq13)
@@ -489,21 +488,21 @@ So, unless your provider can enable this extension, you cannot use FairEmail for
-**(11) Why is POP not supported?**
+**~~(11) Why is POP not supported?~~**
-Besides that any decent email provider supports [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol) these days,
-using [POP](https://en.wikipedia.org/wiki/Post_Office_Protocol) will result in unnecessary extra battery usage and delayed new message notifications.
-Moreover, POP is unsuitable for two way synchronization and more often than not people read and write messages on different devices these days.
+~~Besides that any decent email provider supports [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol) these days,~~
+~~using [POP](https://en.wikipedia.org/wiki/Post_Office_Protocol) will result in unnecessary extra battery usage and delayed new message notifications.~~
+~~Moreover, POP is unsuitable for two way synchronization and more often than not people read and write messages on different devices these days.~~
-Basically, POP supports only downloading and deleting messages from the inbox.
-So, common operations like setting message attributes (read, starred, answered, etc), adding (backing up) and moving messages is not possible.
+~~Basically, POP supports only downloading and deleting messages from the inbox.~~
+~~So, common operations like setting message attributes (read, starred, answered, etc), adding (backing up) and moving messages is not possible.~~
-See also [what Google writes about it](https://support.google.com/mail/answer/7104828).
+~~See also [what Google writes about it](https://support.google.com/mail/answer/7104828).~~
-For example [Gmail can import messages](https://support.google.com/mail/answer/21289) from another POP account,
-which can be used as a workaround for when your provider doesn't support IMAP.
+~~For example [Gmail can import messages](https://support.google.com/mail/answer/21289) from another POP account,~~
+~~which can be used as a workaround for when your provider doesn't support IMAP.~~
-tl;dr; consider to switch to IMAP.
+~~tl;dr; consider to switch to IMAP.~~
diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java
index 08a1173724..5860704fc1 100644
--- a/app/src/main/java/eu/faircode/email/ActivitySetup.java
+++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java
@@ -1025,7 +1025,8 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
}
private void onEditAccount(Intent intent) {
- FragmentAccount fragment = new FragmentAccount();
+ boolean pop = intent.getBooleanExtra("pop", false);
+ FragmentBase fragment = pop ? new FragmentPop() : new FragmentAccount();
fragment.setArguments(intent.getExtras());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
diff --git a/app/src/main/java/eu/faircode/email/AdapterAccount.java b/app/src/main/java/eu/faircode/email/AdapterAccount.java
index ae3dc85814..90876532b4 100644
--- a/app/src/main/java/eu/faircode/email/AdapterAccount.java
+++ b/app/src/main/java/eu/faircode/email/AdapterAccount.java
@@ -192,7 +192,8 @@ public class AdapterAccount extends RecyclerView.Adapter= Build.VERSION_CODES.O) {
+ if (!account.pop && account.notify && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = EntityAccount.getNotificationChannelId(account.id);
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = nm.getNotificationChannel(channelId);
@@ -221,7 +222,7 @@ public class AdapterAccount extends RecyclerView.Adapter messages = db.message().getMessageByMsgId(folder.account, msgid);
+ if (messages.size() > 0) {
+ Log.i(folder.name + " POP having=" + msgid);
+ continue;
+ }
+
+ String authentication = helper.getAuthentication();
+ MessageHelper.MessageParts parts = helper.getMessageParts();
+
+ EntityMessage message = new EntityMessage();
+ message.account = folder.account;
+ message.folder = folder.id;
+ message.uid = null;
+
+ message.msgid = helper.getMessageID();
+ message.references = TextUtils.join(" ", helper.getReferences());
+ message.inreplyto = helper.getInReplyTo();
+ message.deliveredto = helper.getDeliveredTo();
+ message.thread = helper.getThreadId(context, account.id, 0);
+ message.receipt_request = helper.getReceiptRequested();
+ message.receipt_to = helper.getReceiptTo();
+ message.dkim = MessageHelper.getAuthentication("dkim", authentication);
+ message.spf = MessageHelper.getAuthentication("spf", authentication);
+ message.dmarc = MessageHelper.getAuthentication("dmarc", authentication);
+ message.from = helper.getFrom();
+ message.to = helper.getTo();
+ message.cc = helper.getCc();
+ message.bcc = helper.getBcc();
+ message.reply = helper.getReply();
+ message.list_post = helper.getListPost();
+ message.unsubscribe = helper.getListUnsubscribe();
+ message.subject = helper.getSubject();
+ message.size = helper.getSize();
+ message.content = false;
+ message.received = helper.getReceived();
+ message.sent = helper.getSent();
+ message.seen = false;
+ message.answered = false;
+ message.flagged = false;
+ message.flags = null;
+ message.keywords = new String[0];
+ message.ui_seen = false;
+ message.ui_answered = false;
+ message.ui_flagged = false;
+ message.ui_hide = 0L;
+ message.ui_found = false;
+ message.ui_ignored = false;
+ message.ui_browsed = false;
+
+ EntityIdentity identity = matchIdentity(context, folder, message);
+ message.identity = (identity == null ? null : identity.id);
+
+ message.sender = MessageHelper.getSortKey(message.from);
+ Uri lookupUri = ContactInfo.getLookupUri(context, message.from);
+ message.avatar = (lookupUri == null ? null : lookupUri.toString());
+
+ try {
+ db.beginTransaction();
+
+ message.id = db.message().insertMessage(message);
+ Log.i(folder.name + " added id=" + message.id + " uid=" + message.uid);
+
+ int sequence = 1;
+ for (EntityAttachment attachment : parts.getAttachments()) {
+ Log.i(folder.name + " attachment seq=" + sequence +
+ " name=" + attachment.name + " type=" + attachment.type +
+ " cid=" + attachment.cid + " pgp=" + attachment.encryption +
+ " size=" + attachment.size);
+ attachment.message = message.id;
+ attachment.sequence = sequence++;
+ attachment.id = db.attachment().insertAttachment(attachment);
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ String body = parts.getHtml(context);
+ Helper.writeText(message.getFile(context), body);
+ db.message().setMessageContent(message.id,
+ true,
+ parts.isPlainOnly(),
+ HtmlHelper.getPreview(body),
+ parts.getWarnings(message.warning));
+
+ for (EntityAttachment attachment : parts.getAttachments())
+ parts.downloadAttachment(context, attachment);
+
+ } catch (Throwable ex) {
+ db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
+ } finally {
+ ((POP3Message) imessage).invalidate(true);
+ }
+ } finally {
+ db.folder().setFolderSyncState(folder.id, null);
+ }
+ }
+
private static void onSynchronizeMessages(
Context context, JSONArray jargs,
EntityAccount account, final EntityFolder folder,
@@ -1621,18 +1772,6 @@ class Core {
message.warning = Helper.formatThrowable(ex, false);
}
- /*
- // Authentication is more reliable
- Address sender = helper.getSender(); // header
- if (sender != null) {
- String[] s = ((InternetAddress) sender).getAddress().split("@");
- String[] f = (froms == null || froms.length == 0 ? null
- : (((InternetAddress) froms[0]).getAddress()).split("@"));
- if (s.length > 1 && (f == null || (f.length > 1 && !s[1].equals(f[1]))))
- message.warning = context.getString(R.string.title_via, s[1]);
- }
- */
-
try {
db.beginTransaction();
diff --git a/app/src/main/java/eu/faircode/email/DaoFolder.java b/app/src/main/java/eu/faircode/email/DaoFolder.java
index a0fc2cf4e4..66a488330f 100644
--- a/app/src/main/java/eu/faircode/email/DaoFolder.java
+++ b/app/src/main/java/eu/faircode/email/DaoFolder.java
@@ -35,7 +35,8 @@ public interface DaoFolder {
List getFolders(long account, boolean writable, boolean selectable);
@Query("SELECT folder.*" +
- ", account.id AS accountId, account.`order` AS accountOrder, account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
+ ", account.id AS accountId, account.pop AS accountPop, account.`order` AS accountOrder" +
+ ", account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
", COUNT(DISTINCT CASE WHEN rule.enabled THEN rule.id ELSE NULL END) rules" +
", COUNT(DISTINCT CASE WHEN message.ui_hide THEN NULL ELSE message.id END) AS messages" +
", COUNT(DISTINCT CASE WHEN message.content = 1 AND NOT message.ui_hide THEN message.id ELSE NULL END) AS content" +
@@ -71,7 +72,8 @@ public interface DaoFolder {
List getSortedFolders();
@Query("SELECT folder.*" +
- ", account.id AS accountId, account.`order` AS accountOrder, account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
+ ", account.id AS accountId, account.pop AS accountPop, account.`order` AS accountOrder" +
+ ", account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
", COUNT(DISTINCT CASE WHEN rule.enabled THEN rule.id ELSE NULL END) rules" +
", COUNT(DISTINCT CASE WHEN message.ui_hide THEN NULL ELSE message.id END) AS messages" +
", COUNT(DISTINCT CASE WHEN message.content = 1 AND NOT message.ui_hide THEN message.id ELSE NULL END) AS content" +
@@ -90,7 +92,8 @@ public interface DaoFolder {
LiveData> liveFolders(Long account);
@Query("SELECT folder.*" +
- ", account.id AS accountId, account.`order` AS accountOrder, account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
+ ", account.id AS accountId, account.pop AS accountPop, account.`order` AS accountOrder" +
+ ", account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
", COUNT(DISTINCT CASE WHEN rule.enabled THEN rule.id ELSE NULL END) rules" +
", COUNT(DISTINCT CASE WHEN message.ui_hide THEN NULL ELSE message.id END) AS messages" +
", COUNT(DISTINCT CASE WHEN message.content = 1 AND NOT message.ui_hide THEN message.id ELSE NULL END) AS content" +
@@ -126,7 +129,8 @@ public interface DaoFolder {
LiveData liveSynchronizing();
@Query("SELECT folder.*" +
- ", account.id AS accountId, account.`order` AS accountOrder, account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
+ ", account.id AS accountId, account.pop AS accountPop, account.`order` AS accountOrder" +
+ ", account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
", COUNT(DISTINCT CASE WHEN rule.enabled THEN rule.id ELSE NULL END) rules" +
", COUNT(DISTINCT CASE WHEN message.ui_hide THEN NULL ELSE message.id END) AS messages" +
", COUNT(DISTINCT CASE WHEN message.content = 1 AND NOT message.ui_hide THEN message.id ELSE NULL END) AS content" +
diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java
index 1a88e645a7..91133d3f48 100644
--- a/app/src/main/java/eu/faircode/email/DaoMessage.java
+++ b/app/src/main/java/eu/faircode/email/DaoMessage.java
@@ -42,7 +42,7 @@ public interface DaoMessage {
String is_outbox = "folder.type = '" + EntityFolder.OUTBOX + "'";
@Query("SELECT message.*" +
- ", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
+ ", account.pop AS accountPop, account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType, folder.read_only AS folderReadOnly" +
", identity.name AS identityName, identity.email AS identityEmail, identity.synchronize AS identitySynchronize" +
", '[' || group_concat(message.`from`, ',') || ']' AS senders" +
@@ -90,7 +90,7 @@ public interface DaoMessage {
boolean debug);
@Query("SELECT message.*" +
- ", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
+ ", account.pop AS accountPop, account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType, folder.read_only AS folderReadOnly" +
", identity.name AS identityName, identity.email AS identityEmail, identity.synchronize AS identitySynchronize" +
", '[' || group_concat(message.`from`, ',') || ']' AS senders" +
@@ -132,7 +132,7 @@ public interface DaoMessage {
boolean debug);
@Query("SELECT message.*" +
- ", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
+ ", account.pop AS accountPop, account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType, folder.read_only AS folderReadOnly" +
", identity.name AS identityName, identity.email AS identityEmail, identity.synchronize AS identitySynchronize" +
", message.`from` AS senders" +
@@ -231,7 +231,7 @@ public interface DaoMessage {
int countMessageByMsgId(long folder, String msgid);
@Query("SELECT message.*" +
- ", account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
+ ", account.pop AS accountPop, account.name AS accountName, identity.color AS accountColor, account.notify AS accountNotify" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType, folder.read_only AS folderReadOnly" +
", identity.name AS identityName, identity.email AS identityEmail, identity.synchronize AS identitySynchronize" +
", message.`from` AS senders" +
@@ -249,7 +249,7 @@ public interface DaoMessage {
LiveData liveMessage(long id);
@Query("SELECT message.*" +
- ", account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
+ ", account.pop AS accountPop, account.name AS accountName, IFNULL(identity.color, account.color) AS accountColor, account.notify AS accountNotify" +
", folder.name AS folderName, folder.display AS folderDisplay, folder.type AS folderType, folder.read_only AS folderReadOnly" +
", identity.name AS identityName, identity.email AS identityEmail, identity.synchronize AS identitySynchronize" +
", message.`from` AS senders" +
diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java
index 3bbdcd45f4..a66c25fddc 100644
--- a/app/src/main/java/eu/faircode/email/EntityAccount.java
+++ b/app/src/main/java/eu/faircode/email/EntityAccount.java
@@ -103,7 +103,7 @@ public class EntityAccount extends EntityOrder implements Serializable {
public Long last_connected;
String getProtocol() {
- return "imap" + (starttls ? "" : "s");
+ return (pop ? "pop3" : "imap") + (starttls ? "" : "s");
}
static String getNotificationChannelId(long id) {
@@ -146,6 +146,7 @@ public class EntityAccount extends EntityOrder implements Serializable {
JSONObject json = new JSONObject();
json.put("id", id);
json.put("order", order);
+ json.put("pop", pop);
json.put("host", host);
json.put("starttls", starttls);
json.put("insecure", insecure);
@@ -184,6 +185,9 @@ public class EntityAccount extends EntityOrder implements Serializable {
if (json.has("order"))
account.order = json.getInt("order");
+ if (json.has("pop"))
+ account.pop = json.getBoolean("pop");
+
account.host = json.getString("host");
account.starttls = (json.has("starttls") && json.getBoolean("starttls"));
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java
index f88127eda6..ff444b1de8 100644
--- a/app/src/main/java/eu/faircode/email/EntityOperation.java
+++ b/app/src/main/java/eu/faircode/email/EntityOperation.java
@@ -79,7 +79,6 @@ public class EntityOperation {
static final String COPY = "copy";
static final String FETCH = "fetch";
static final String DELETE = "delete";
- static final String DELETED = "deleted";
static final String SEEN = "seen";
static final String ANSWERED = "answered";
static final String FLAG = "flag";
@@ -228,7 +227,7 @@ public class EntityOperation {
name = RAW;
} else if (DELETE.equals(name))
- db.message().setMessageUiHide(message.id, new Date().getTime());
+ db.message().setMessageUiHide(message.id, Long.MAX_VALUE);
} catch (JSONException ex) {
Log.e(ex);
diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java
index 7dcb840f77..ea65690dfb 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAccount.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java
@@ -84,7 +84,6 @@ public class FragmentAccount extends FragmentBase {
private Button btnAutoConfig;
private ContentLoadingProgressBar pbAutoConfig;
- private TextView tvPopSupport;
private TextView tvActiveSyncSupport;
private EditText etHost;
private RadioGroup rgEncryption;
@@ -181,7 +180,6 @@ public class FragmentAccount extends FragmentBase {
btnAutoConfig = view.findViewById(R.id.btnAutoConfig);
pbAutoConfig = view.findViewById(R.id.pbAutoConfig);
- tvPopSupport = view.findViewById(R.id.tvPopSupport);
tvActiveSyncSupport = view.findViewById(R.id.tvActiveSyncSupport);
etHost = view.findViewById(R.id.etHost);
etPort = view.findViewById(R.id.etPort);
@@ -279,14 +277,6 @@ public class FragmentAccount extends FragmentBase {
}
});
- tvPopSupport.setPaintFlags(tvPopSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
- tvPopSupport.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Helper.viewFAQ(getContext(), 11);
- }
- });
-
tvActiveSyncSupport.setPaintFlags(tvActiveSyncSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvActiveSyncSupport.setOnClickListener(new View.OnClickListener() {
@Override
@@ -586,7 +576,7 @@ public class FragmentAccount extends FragmentBase {
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
iservice.connect(host, Integer.parseInt(port), auth, user, password);
- result.idle = iservice.getStore().hasCapability("IDLE");
+ result.idle = iservice.hasCapability("IDLE");
boolean inbox = false;
@@ -1164,7 +1154,9 @@ public class FragmentAccount extends FragmentBase {
@Override
protected EntityAccount onExecute(Context context, Bundle args) {
long id = args.getLong("id");
- return DB.getInstance(context).account().getAccount(id);
+
+ DB db = DB.getInstance(context);
+ return db.account().getAccount(id);
}
@Override
diff --git a/app/src/main/java/eu/faircode/email/FragmentAccounts.java b/app/src/main/java/eu/faircode/email/FragmentAccounts.java
index 7c673f013b..48b88051a5 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAccounts.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAccounts.java
@@ -34,6 +34,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.Group;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentTransaction;
@@ -108,11 +109,37 @@ public class FragmentAccounts extends FragmentBase {
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- FragmentAccount fragment = new FragmentAccount();
- fragment.setArguments(new Bundle());
- FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
- fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
- fragmentTransaction.commit();
+ PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), fab);
+
+ popupMenu.getMenu().add(Menu.NONE, R.string.title_imap, 1, R.string.title_imap)
+ .setEnabled(Helper.hasValidFingerprint(getContext()));
+ popupMenu.getMenu().add(Menu.NONE, R.string.title_pop3, 2, R.string.title_pop3);
+
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.string.title_imap:
+ onCreate(true);
+ return true;
+ case R.string.title_pop3:
+ onCreate(false);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void onCreate(boolean imap) {
+ FragmentBase fragment = imap ? new FragmentAccount() : new FragmentPop();
+ fragment.setArguments(new Bundle());
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
+ fragmentTransaction.commit();
+ }
+ });
+
+ popupMenu.show();
}
});
diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java
index b8641326f5..b6643464cf 100644
--- a/app/src/main/java/eu/faircode/email/FragmentCompose.java
+++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java
@@ -2187,8 +2187,6 @@ public class FragmentCompose extends FragmentBase {
draft.plain_only = ref.plain_only;
if (answer > 0)
body = EntityAnswer.getAnswerText(context, answer, draft.to) + body;
-
- EntityOperation.queue(context, ref, EntityOperation.SEEN, true);
}
if (plain_only)
diff --git a/app/src/main/java/eu/faircode/email/FragmentFolders.java b/app/src/main/java/eu/faircode/email/FragmentFolders.java
index fd2a5f91c6..48c0ee357e 100644
--- a/app/src/main/java/eu/faircode/email/FragmentFolders.java
+++ b/app/src/main/java/eu/faircode/email/FragmentFolders.java
@@ -270,7 +270,7 @@ public class FragmentFolders extends FragmentBase {
else
fabError.hide();
- if (account == null)
+ if (account == null || account.pop)
fab.hide();
else
fab.show();
diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java
index 31d7a3c4ea..07063bd660 100644
--- a/app/src/main/java/eu/faircode/email/FragmentMessages.java
+++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java
@@ -838,8 +838,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
int order = 1;
for (EntityAccount account : accounts)
- popupMenu.getMenu().add(Menu.NONE, 0, order++, account.name)
- .setIntent(new Intent().putExtra("account", account.id));
+ if (!account.pop)
+ popupMenu.getMenu().add(Menu.NONE, 0, order++, account.name)
+ .setIntent(new Intent().putExtra("account", account.id));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
@@ -1749,7 +1750,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (result.hasTrash == null) result.hasTrash = false;
if (result.hasJunk == null) result.hasJunk = false;
- result.accounts = db.account().getSynchronizingAccounts();
+ result.accounts = new ArrayList<>();
+ for (EntityAccount account : db.account().getSynchronizingAccounts())
+ if (!account.pop)
+ result.accounts.add(account);
return result;
}
@@ -3211,7 +3215,14 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (folder == null)
return null;
- if (message.uid != null) {
+ EntityAccount account = db.account().getAccount(folder.account);
+ if (account == null)
+ return null;
+
+ if (message.uid == null) {
+ if (expand_read && !message.ui_seen && account.pop)
+ EntityOperation.queue(context, message, EntityOperation.SEEN, true);
+ } else {
if (!message.content)
EntityOperation.queue(context, message, EntityOperation.BODY);
diff --git a/app/src/main/java/eu/faircode/email/FragmentPop.java b/app/src/main/java/eu/faircode/email/FragmentPop.java
new file mode 100644
index 0000000000..743be037a0
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/FragmentPop.java
@@ -0,0 +1,605 @@
+package eu.faircode.email;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.textfield.TextInputLayout;
+
+import java.util.Date;
+import java.util.List;
+
+import static android.app.Activity.RESULT_OK;
+import static com.google.android.material.textfield.TextInputLayout.END_ICON_NONE;
+import static com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE;
+
+public class FragmentPop extends FragmentBase {
+ private ViewGroup view;
+ private ScrollView scroll;
+
+ private EditText etHost;
+ private RadioGroup rgEncryption;
+ private CheckBox cbInsecure;
+ private EditText etPort;
+ private EditText etUser;
+ private TextInputLayout tilPassword;
+
+ private EditText etName;
+ private Button btnColor;
+ private View vwColor;
+ private ImageButton ibColorDefault;
+ private TextView tvColorPro;
+
+ private CheckBox cbSynchronize;
+ private CheckBox cbPrimary;
+ private EditText etInterval;
+
+ private Button btnSave;
+ private ContentLoadingProgressBar pbSave;
+ private TextView tvError;
+
+ private ContentLoadingProgressBar pbWait;
+
+ private long id = -1;
+ private boolean saving = false;
+ private int color = Color.TRANSPARENT;
+
+ private static final int REQUEST_COLOR = 1;
+ private static final int REQUEST_DELETE = 2;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Get arguments
+ Bundle args = getArguments();
+ id = args.getLong("id", -1);
+ }
+
+ @Override
+ @Nullable
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ setSubtitle(R.string.title_edit_account);
+ setHasOptionsMenu(true);
+
+ view = (ViewGroup) inflater.inflate(R.layout.fragment_pop, container, false);
+ scroll = view.findViewById(R.id.scroll);
+
+ // Get controls
+ etHost = view.findViewById(R.id.etHost);
+ etPort = view.findViewById(R.id.etPort);
+ rgEncryption = view.findViewById(R.id.rgEncryption);
+ cbInsecure = view.findViewById(R.id.cbInsecure);
+ etUser = view.findViewById(R.id.etUser);
+ tilPassword = view.findViewById(R.id.tilPassword);
+
+ etName = view.findViewById(R.id.etName);
+ btnColor = view.findViewById(R.id.btnColor);
+ vwColor = view.findViewById(R.id.vwColor);
+ ibColorDefault = view.findViewById(R.id.ibColorDefault);
+ tvColorPro = view.findViewById(R.id.tvColorPro);
+
+ cbSynchronize = view.findViewById(R.id.cbSynchronize);
+ cbPrimary = view.findViewById(R.id.cbPrimary);
+ etInterval = view.findViewById(R.id.etInterval);
+
+ btnSave = view.findViewById(R.id.btnSave);
+ pbSave = view.findViewById(R.id.pbSave);
+
+ tvError = view.findViewById(R.id.tvError);
+
+ pbWait = view.findViewById(R.id.pbWait);
+
+ setColor(color);
+ btnColor.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FragmentDialogColor fragment = new FragmentDialogColor();
+ fragment.initialize(R.string.title_color, color, new Bundle(), getContext());
+ fragment.setTargetFragment(FragmentPop.this, REQUEST_COLOR);
+ fragment.show(getFragmentManager(), "account:color");
+ }
+ });
+
+ ibColorDefault.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setColor(Color.TRANSPARENT);
+ }
+ });
+
+ Helper.linkPro(tvColorPro);
+
+ cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ cbPrimary.setEnabled(checked);
+ }
+ });
+
+ btnSave.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onSave();
+ }
+ });
+
+ // Initialize
+ Helper.setViewsEnabled(view, false);
+
+ tilPassword.setEndIconMode(id < 0 ? END_ICON_PASSWORD_TOGGLE : END_ICON_NONE);
+
+ pbSave.setVisibility(View.GONE);
+ tvError.setVisibility(View.GONE);
+
+ return view;
+ }
+
+ private void onSave() {
+ Bundle args = new Bundle();
+ args.putLong("id", id);
+
+ args.putString("host", etHost.getText().toString());
+ args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
+ args.putBoolean("insecure", cbInsecure.isChecked());
+ args.putString("port", etPort.getText().toString());
+ args.putString("user", etUser.getText().toString());
+ args.putString("password", tilPassword.getEditText().getText().toString());
+
+ args.putString("name", etName.getText().toString());
+ args.putInt("color", color);
+
+ args.putBoolean("primary", cbPrimary.isChecked());
+ args.putBoolean("synchronize", cbSynchronize.isChecked());
+ args.putString("interval", etInterval.getText().toString());
+
+ new SimpleTask() {
+ @Override
+ protected void onPreExecute(Bundle args) {
+ saving = true;
+ getActivity().invalidateOptionsMenu();
+ Helper.setViewsEnabled(view, false);
+ tvError.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected void onPostExecute(Bundle args) {
+ saving = false;
+ getActivity().invalidateOptionsMenu();
+ Helper.setViewsEnabled(view, true);
+ pbSave.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected Boolean onExecute(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").trim();
+ String password = args.getString("password");
+
+ String name = args.getString("name");
+ Integer color = args.getInt("color");
+
+ boolean synchronize = args.getBoolean("synchronize");
+ boolean primary = args.getBoolean("primary");
+ String interval = args.getString("interval");
+
+ boolean pro = ActivityBilling.isPro(context);
+
+ if (host.contains(":")) {
+ Uri h = Uri.parse(host);
+ host = h.getHost();
+ }
+
+ if (TextUtils.isEmpty(host))
+ throw new IllegalArgumentException(context.getString(R.string.title_no_host));
+ if (TextUtils.isEmpty(port))
+ port = "995";
+ if (TextUtils.isEmpty(user))
+ throw new IllegalArgumentException(context.getString(R.string.title_no_user));
+ if (synchronize && TextUtils.isEmpty(password) && !insecure)
+ throw new IllegalArgumentException(context.getString(R.string.title_no_password));
+ if (TextUtils.isEmpty(interval))
+ interval = Integer.toString(EntityAccount.DEFAULT_KEEP_ALIVE_INTERVAL);
+
+ if (TextUtils.isEmpty(name))
+ name = user;
+ if (color == Color.TRANSPARENT || !pro)
+ color = null;
+
+ long now = new Date().getTime();
+
+ DB db = DB.getInstance(context);
+ EntityAccount account = db.account().getAccount(id);
+
+ boolean check = (synchronize && (account == null ||
+ !account.synchronize || account.error != null ||
+ !account.insecure.equals(insecure) ||
+ !host.equals(account.host) || Integer.parseInt(port) != account.port ||
+ !user.equals(account.user) || !password.equals(account.password)));
+ boolean reload = (check || account == null ||
+ account.synchronize != synchronize ||
+ !account.poll_interval.equals(Integer.parseInt(interval)));
+ Log.i("Account check=" + check + " reload=" + reload);
+
+ Long last_connected = null;
+ if (account != null && synchronize == account.synchronize)
+ last_connected = account.last_connected;
+
+ // Check POP3 server
+ if (check) {
+ String protocol = "pop3" + (starttls ? "" : "s");
+ try (MailService iservice = new MailService(context, protocol, null, insecure, true)) {
+ iservice.connect(host, Integer.parseInt(port), MailService.AUTH_TYPE_PASSWORD, user, password);
+ }
+ }
+
+ try {
+ db.beginTransaction();
+
+ if (account != null && !account.password.equals(password)) {
+ List identities = db.identity().getIdentities(account.id);
+ for (EntityIdentity identity : identities)
+ if (identity.password.equals(account.password) &&
+ ConnectionHelper.isSameDomain(identity.host, account.host)) {
+ Log.i("Changing identity password host=" + identity.host);
+ identity.password = password;
+ db.identity().updateIdentity(identity);
+ }
+ }
+
+ boolean update = (account != null);
+ if (account == null)
+ account = new EntityAccount();
+
+ account.pop = true;
+ account.host = host;
+ account.starttls = starttls;
+ account.insecure = insecure;
+ account.port = Integer.parseInt(port);
+ account.auth_type = MailService.AUTH_TYPE_PASSWORD;
+ account.user = user;
+ account.password = password;
+
+ account.name = name;
+ account.color = color;
+
+ account.synchronize = synchronize;
+ account.primary = (account.synchronize && primary);
+ account.browse = false;
+ account.poll_interval = Integer.parseInt(interval);
+
+ if (!update)
+ account.created = now;
+
+ account.warning = null;
+ account.error = null;
+ account.last_connected = last_connected;
+
+ if (account.primary)
+ db.account().resetPrimary();
+
+ if (update)
+ db.account().updateAccount(account);
+ else
+ account.id = db.account().insertAccount(account);
+ EntityLog.log(context, (update ? "Updated" : "Added") + " account=" + account.name);
+
+
+ EntityFolder inbox = db.folder().getFolderByType(account.id, EntityFolder.INBOX);
+ if (inbox == null) {
+ inbox = new EntityFolder();
+ inbox.account = account.id;
+ inbox.name = "INBOX";
+ inbox.type = EntityFolder.INBOX;
+ inbox.synchronize = true;
+ inbox.unified = true;
+ inbox.notify = true;
+ inbox.sync_days = Integer.MAX_VALUE;
+ inbox.keep_days = Integer.MAX_VALUE;
+ inbox.initialize = 0;
+ inbox.id = db.folder().insertFolder(inbox);
+ }
+
+ EntityFolder drafts = db.folder().getFolderByType(account.id, EntityFolder.DRAFTS);
+ if (drafts == null) {
+ drafts = new EntityFolder();
+ drafts.account = account.id;
+ drafts.name = context.getString(R.string.title_folder_drafts);
+ drafts.type = EntityFolder.DRAFTS;
+ drafts.synchronize = false;
+ drafts.unified = false;
+ drafts.notify = false;
+ drafts.sync_days = Integer.MAX_VALUE;
+ drafts.keep_days = Integer.MAX_VALUE;
+ drafts.initialize = 0;
+ drafts.id = db.folder().insertFolder(drafts);
+ }
+
+ EntityFolder sent = db.folder().getFolderByType(account.id, EntityFolder.SENT);
+ if (sent == null) {
+ sent = new EntityFolder();
+ sent.account = account.id;
+ sent.name = context.getString(R.string.title_folder_sent);
+ sent.type = EntityFolder.SENT;
+ sent.synchronize = false;
+ sent.unified = false;
+ sent.notify = false;
+ sent.sync_days = Integer.MAX_VALUE;
+ sent.keep_days = Integer.MAX_VALUE;
+ sent.initialize = 0;
+ sent.id = db.folder().insertFolder(sent);
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ if (reload)
+ ServiceSynchronize.reload(context, "save account");
+
+ if (!synchronize) {
+ NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel("receive:" + account.id, 1);
+ nm.cancel("alert:" + account.id, 1);
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, Boolean dirty) {
+ getFragmentManager().popBackStack();
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+ if (ex instanceof IllegalArgumentException)
+ Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
+ else {
+ tvError.setText(Helper.formatThrowable(ex, false));
+ tvError.setVisibility(View.VISIBLE);
+
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ scroll.smoothScrollTo(0, tvError.getBottom());
+ }
+ });
+ }
+ }
+ }.execute(this, args, "account:save");
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putString("fair:password", tilPassword.getEditText().getText().toString());
+ outState.putInt("fair:color", color);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ Bundle args = new Bundle();
+ args.putLong("id", id);
+
+ new SimpleTask() {
+ @Override
+ protected EntityAccount onExecute(Context context, Bundle args) {
+ long id = args.getLong("id");
+
+ DB db = DB.getInstance(context);
+ return db.account().getAccount(id);
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, final EntityAccount account) {
+ if (savedInstanceState == null) {
+ etHost.setText(account == null ? null : account.host);
+ etPort.setText(account == null ? null : Long.toString(account.port));
+
+ rgEncryption.check(account != null && account.starttls ? R.id.radio_starttls : R.id.radio_ssl);
+ cbInsecure.setChecked(account == null ? false : account.insecure);
+
+ etUser.setText(account == null ? null : account.user);
+ tilPassword.getEditText().setText(account == null ? null : account.password);
+
+ etName.setText(account == null ? null : account.name);
+
+ cbSynchronize.setChecked(account == null ? true : account.synchronize);
+ cbPrimary.setChecked(account == null ? false : account.primary);
+ etInterval.setText(account == null ? "" : Long.toString(account.poll_interval));
+
+ color = (account == null || account.color == null ? Color.TRANSPARENT : account.color);
+
+ new SimpleTask() {
+ @Override
+ protected EntityAccount onExecute(Context context, Bundle args) {
+ return DB.getInstance(context).account().getPrimaryAccount();
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, EntityAccount primary) {
+ if (primary == null)
+ cbPrimary.setChecked(true);
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+ Helper.unexpectedError(getFragmentManager(), ex);
+ }
+ }.execute(FragmentPop.this, new Bundle(), "account:primary");
+ } else {
+ tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
+ color = savedInstanceState.getInt("fair:color");
+ }
+
+ setColor(color);
+ cbPrimary.setEnabled(cbSynchronize.isChecked());
+
+ Helper.setViewsEnabled(view, true);
+
+ pbWait.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+ Helper.unexpectedError(getFragmentManager(), ex);
+ }
+ }.execute(this, args, "account:get");
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.menu_account, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_delete).setVisible(id > 0 && !saving);
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_delete:
+ onMenuDelete();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void setColor(int color) {
+ this.color = color;
+
+ GradientDrawable border = new GradientDrawable();
+ border.setColor(color);
+ border.setStroke(1, Helper.resolveColor(getContext(), R.attr.colorSeparator));
+ vwColor.setBackground(border);
+ }
+
+ private void onMenuDelete() {
+ Bundle aargs = new Bundle();
+ aargs.putString("question", getString(R.string.title_account_delete));
+
+ FragmentDialogAsk fragment = new FragmentDialogAsk();
+ fragment.setArguments(aargs);
+ fragment.setTargetFragment(FragmentPop.this, REQUEST_DELETE);
+ fragment.show(getFragmentManager(), "account:delete");
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ try {
+ switch (requestCode) {
+ case REQUEST_COLOR:
+ if (resultCode == RESULT_OK && data != null) {
+ if (ActivityBilling.isPro(getContext())) {
+ Bundle args = data.getBundleExtra("args");
+ setColor(args.getInt("color"));
+ } else
+ startActivity(new Intent(getContext(), ActivityBilling.class));
+ }
+ break;
+ case REQUEST_DELETE:
+ if (resultCode == RESULT_OK)
+ onDelete();
+ break;
+ }
+ } catch (Throwable ex) {
+ Log.e(ex);
+ }
+ }
+
+ private void onDelete() {
+ Bundle args = new Bundle();
+ args.putLong("id", id);
+
+ new SimpleTask() {
+ @Override
+ protected void onPostExecute(Bundle args) {
+ Helper.setViewsEnabled(view, false);
+ pbWait.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ protected Void onExecute(Context context, Bundle args) {
+ long id = args.getLong("id");
+
+ DB db = DB.getInstance(context);
+ db.account().setAccountTbd(id);
+
+ ServiceSynchronize.reload(context, "delete account");
+
+ return null;
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, Void data) {
+ if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
+ getFragmentManager().popBackStack();
+ }
+
+ @Override
+ protected void onException(Bundle args, Throwable ex) {
+ Helper.unexpectedError(getFragmentManager(), ex);
+ }
+ }.execute(this, args, "account:delete");
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java
index 43e50d3454..799c52c895 100644
--- a/app/src/main/java/eu/faircode/email/MailService.java
+++ b/app/src/main/java/eu/faircode/email/MailService.java
@@ -29,6 +29,7 @@ import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Service;
import javax.mail.Session;
+import javax.mail.Store;
public class MailService implements AutoCloseable {
private Context context;
@@ -66,7 +67,22 @@ public class MailService implements AutoCloseable {
String checkserveridentity = Boolean.toString(!insecure).toLowerCase();
- if ("imap".equals(protocol) || "imaps".equals(protocol)) {
+ if ("pop3".equals(protocol) || "pop3s".equals(protocol)) {
+ // https://javaee.github.io/javamail/docs/api/com/sun/mail/pop3/package-summary.html#properties
+ properties.put("mail." + protocol + ".ssl.checkserveridentity", checkserveridentity);
+ properties.put("mail." + protocol + ".ssl.trust", "*");
+
+ properties.put("mail.pop3s.starttls.enable", "false");
+
+ properties.put("mail.pop3.starttls.enable", "true");
+ properties.put("mail.pop3.starttls.required", "true");
+
+ // TODO: make timeouts configurable?
+ properties.put("mail." + protocol + ".connectiontimeout", Integer.toString(CONNECT_TIMEOUT));
+ properties.put("mail." + protocol + ".writetimeout", Integer.toString(WRITE_TIMEOUT)); // one thread overhead
+ properties.put("mail." + protocol + ".timeout", Integer.toString(READ_TIMEOUT));
+
+ } else if ("imap".equals(protocol) || "imaps".equals(protocol)) {
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
properties.put("mail." + protocol + ".ssl.checkserveridentity", checkserveridentity);
properties.put("mail." + protocol + ".ssl.trust", "*");
@@ -218,17 +234,22 @@ public class MailService implements AutoCloseable {
isession.setDebug(debug);
//System.setProperty("mail.socket.debug", Boolean.toString(debug));
- if ("imap".equals(protocol) || "imaps".equals(protocol)) {
+ if ("pop3".equals(protocol) || "pop3s".equals(protocol)) {
+ iservice = isession.getStore(protocol);
+ iservice.connect(host, port, user, password);
+
+ } else if ("imap".equals(protocol) || "imaps".equals(protocol)) {
iservice = isession.getStore(protocol);
iservice.connect(host, port, user, password);
// https://www.ietf.org/rfc/rfc2971.txt
- if (getStore().hasCapability("ID"))
+ IMAPStore istore = (IMAPStore) getStore();
+ if (istore.hasCapability("ID"))
try {
Map id = new LinkedHashMap<>();
id.put("name", context.getString(R.string.app_name));
id.put("version", BuildConfig.VERSION_NAME);
- Map sid = getStore().id(id);
+ Map sid = istore.id(id);
if (sid != null) {
Map crumb = new HashMap<>();
for (String key : sid.keySet()) {
@@ -322,14 +343,22 @@ public class MailService implements AutoCloseable {
return folders;
}
- IMAPStore getStore() {
- return (IMAPStore) iservice;
+ Store getStore() {
+ return (Store) iservice;
}
SMTPTransport getTransport() {
return (SMTPTransport) iservice;
}
+ boolean hasCapability(String capability) throws MessagingException {
+ Store store = getStore();
+ if (store instanceof IMAPStore)
+ return ((IMAPStore) getStore()).hasCapability(capability);
+ else
+ return false;
+ }
+
public void close() throws MessagingException {
try {
if (iservice != null)
diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
index bba31104f9..f02dcd81c8 100644
--- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
+++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java
@@ -701,7 +701,7 @@ public class ServiceSynchronize extends ServiceBase {
throw ex;
}
- final boolean capIdle = iservice.getStore().hasCapability("IDLE");
+ final boolean capIdle = iservice.hasCapability("IDLE");
Log.i(account.name + " idle=" + capIdle);
db.account().setAccountState(account.id, "connected");
@@ -792,7 +792,8 @@ public class ServiceSynchronize extends ServiceBase {
});
// Update folder list
- Core.onSynchronizeFolders(this, account, iservice.getStore(), state);
+ if (!account.pop)
+ Core.onSynchronizeFolders(this, account, iservice.getStore(), state);
// Open synchronizing folders
final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
@@ -993,12 +994,13 @@ public class ServiceSynchronize extends ServiceBase {
// Get folder
Folder ifolder = mapFolders.get(folder); // null when polling
- final boolean shouldClose = (ifolder == null);
+ boolean canOpen = (!account.pop || EntityFolder.INBOX.equals(folder.type));
+ final boolean shouldClose = (ifolder == null && canOpen);
try {
Log.i(folder.name + " run " + (shouldClose ? "offline" : "online"));
- if (ifolder == null) {
+ if (shouldClose) {
// Prevent unnecessary folder connections
if (db.operation().getOperationCount(folder.id, null) == 0)
return;
diff --git a/app/src/main/java/eu/faircode/email/TupleFolderEx.java b/app/src/main/java/eu/faircode/email/TupleFolderEx.java
index d44394ded0..0e34eb1ceb 100644
--- a/app/src/main/java/eu/faircode/email/TupleFolderEx.java
+++ b/app/src/main/java/eu/faircode/email/TupleFolderEx.java
@@ -38,6 +38,7 @@ import java.util.Objects;
public class TupleFolderEx extends EntityFolder implements Serializable {
public Long accountId;
+ public Boolean accountPop;
public Integer accountOrder;
public String accountName;
public Integer accountColor;
@@ -66,6 +67,7 @@ public class TupleFolderEx extends EntityFolder implements Serializable {
TupleFolderEx other = (TupleFolderEx) obj;
return (super.equals(obj) &&
Objects.equals(this.accountId, other.accountId) &&
+ Objects.equals(this.accountPop, other.accountPop) &&
Objects.equals(this.accountName, other.accountName) &&
Objects.equals(this.accountColor, other.accountColor) &&
Objects.equals(this.accountState, other.accountState) &&
diff --git a/app/src/main/java/eu/faircode/email/TupleMessageEx.java b/app/src/main/java/eu/faircode/email/TupleMessageEx.java
index d9929e84df..006c114c8a 100644
--- a/app/src/main/java/eu/faircode/email/TupleMessageEx.java
+++ b/app/src/main/java/eu/faircode/email/TupleMessageEx.java
@@ -26,6 +26,7 @@ import java.util.Objects;
import javax.mail.Address;
public class TupleMessageEx extends EntityMessage {
+ public boolean accountPop;
public String accountName;
public Integer accountColor;
public boolean accountNotify;
@@ -52,6 +53,7 @@ public class TupleMessageEx extends EntityMessage {
if (obj instanceof TupleMessageEx) {
TupleMessageEx other = (TupleMessageEx) obj;
return (super.equals(obj) &&
+ this.accountPop == other.accountPop &&
Objects.equals(this.accountName, other.accountName) &&
Objects.equals(this.accountColor, other.accountColor) &&
this.accountNotify == other.accountNotify &&
diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml
index 5d9454e056..74eb97120b 100644
--- a/app/src/main/res/layout/fragment_account.xml
+++ b/app/src/main/res/layout/fragment_account.xml
@@ -101,19 +101,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnAutoConfig" />
-
-
+ app:layout_constraintTop_toBottomOf="@id/tvImap" />
@@ -806,7 +793,7 @@
android:layout_height="0dp"
app:constraint_referenced_ids="
tvDomain,tvDomainHint,etDomain,btnAutoConfig,
- tvImap,tvPopSupport,tvActiveSyncSupport,tvHost,etHost,rgEncryption,cbInsecure,tvPort,etPort" />
+ tvImap,tvActiveSyncSupport,tvHost,etHost,rgEncryption,cbInsecure,tvPort,etPort" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 575eacca9a..20e1f69063 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -415,7 +415,6 @@
Delete this identity permanently?
Edit as HTML
Last connected: %1$s
- POP3 is not supported
ActiveSync is not supported
OAuth is not supported
Authorize