diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 1234cdc3bd..9534c94a52 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -987,12 +987,7 @@ public class AdapterMessage extends RecyclerView.Adapter addressCache = new ConcurrentHashMap<>(); + protected static final ConcurrentHashMap inverseAddressCache = new ConcurrentHashMap<>(); + + public static String getCacheStats() { + return "AddressCache: " + addressCache.mappingCount() + System.lineSeparator() + + "inverseCache: " + inverseAddressCache.mappingCount(); + } + public abstract DaoAccount account(); public abstract DaoIdentity identity(); @@ -1034,25 +1050,39 @@ public abstract class DB extends RoomDatabase { if (addresses == null) return null; JSONArray jaddresses = new JSONArray(); - for (Address address : addresses) - try { - if (address instanceof InternetAddress) { - String a = ((InternetAddress) address).getAddress(); - String p = ((InternetAddress) address).getPersonal(); - JSONObject jaddress = new JSONObject(); - if (a != null) - jaddress.put("address", a); - if (p != null) - jaddress.put("personal", p); - jaddresses.put(jaddress); - } else { - JSONObject jaddress = new JSONObject(); - jaddress.put("address", address.toString()); - jaddresses.put(jaddress); - } - } catch (JSONException ex) { - Log.e(ex); + for (Address address : addresses) { + JSONObject jaddress = inverseAddressCache.get(address); + if (jaddress == null) { + try { + jaddress = new JSONObject(); + InternetAddress internetAddress = null; + if (address instanceof InternetAddress) { + internetAddress = (InternetAddress) address; + String a = internetAddress.getAddress(); + String p = internetAddress.getPersonal(); + if (a != null) + jaddress.put("address", a); + if (p != null) + jaddress.put("personal", p); + } else { + jaddress.put("address", address.toString()); + } + JSONAddress key = new JSONAddress(jaddress); + if (!(internetAddress instanceof InternetAddressImpl)) { + address = new InternetAddressImpl(internetAddress); + } + synchronized (addressCache) { + addressCache.put(key, address); + inverseAddressCache.put(address, key); + } + } catch (JSONException | UnsupportedEncodingException ex) { + Log.e(ex); + } + } else { + jaddress = ((JSONAddress) jaddress).jsonObject; } + jaddresses.put(jaddress); + } return jaddresses.toString(); } @@ -1074,12 +1104,21 @@ public abstract class DB extends RoomDatabase { } for (int i = 0; i < jaddresses.length(); i++) { JSONObject jaddress = (JSONObject) jaddresses.get(i); - String email = jaddress.getString("address"); - String personal = jaddress.optString("personal"); - if (TextUtils.isEmpty(personal)) - result.add(new InternetAddress(email)); - else - result.add(new InternetAddress(email, personal)); + JSONAddress key = new JSONAddress(jaddress); + Address address = addressCache.get(key); + if (address == null) { + String email = jaddress.getString("address"); + String personal = jaddress.optString("personal"); + if (TextUtils.isEmpty(personal)) + address = new InternetAddressImpl(email); + else + address = new InternetAddressImpl(email, personal); + synchronized (addressCache) { + addressCache.put(key, address); + inverseAddressCache.put(address, key); + } + } + result.add(address); } } catch (Throwable ex) { // Compose can store invalid addresses diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java index 38ddad86ae..3fcb14078a 100644 --- a/app/src/main/java/eu/faircode/email/EntityRule.java +++ b/app/src/main/java/eu/faircode/email/EntityRule.java @@ -46,6 +46,7 @@ import javax.mail.Header; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; import static androidx.room.ForeignKey.CASCADE; @@ -323,7 +324,7 @@ public class EntityRule { reply.inreplyto = message.msgid; reply.thread = message.thread; reply.to = (message.reply == null || message.reply.length == 0 ? message.from : message.reply); - reply.from = new InternetAddress[]{new InternetAddress(identity.email, identity.name)}; + reply.from = new InternetAddressImpl[]{new InternetAddressImpl(identity.email, identity.name)}; if (cc) reply.cc = message.cc; reply.subject = context.getString(R.string.title_subject_reply, message.subject == null ? "" : message.subject); diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 2376a9e573..2dc8e527fe 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -143,6 +143,7 @@ import javax.mail.Part; import javax.mail.Session; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; import javax.mail.internet.MimeMessage; import static android.app.Activity.RESULT_OK; @@ -1240,7 +1241,7 @@ public class FragmentCompose extends FragmentBase { if (address != null) list.addAll(Arrays.asList(address)); - list.add(new InternetAddress(email, name)); + list.add(new InternetAddressImpl(email, name)); if (requestCode == REQUEST_CONTACT_TO) draft.to = list.toArray(new Address[0]); @@ -1622,7 +1623,7 @@ public class FragmentCompose extends FragmentBase { if (contact != null && contact.moveToNext()) { String name = contact.getString(0); String email = contact.getString(1); - selected.add(new InternetAddress(email, name)); + selected.add(new InternetAddressImpl(email, name)); } } } @@ -2078,7 +2079,7 @@ public class FragmentCompose extends FragmentBase { String via = null; if (ref.identity != null) { EntityIdentity identity = db.identity().getIdentity(ref.identity); - draft.from = new Address[]{new InternetAddress(identity.email, identity.name)}; + draft.from = new Address[]{new InternetAddressImpl(identity.email, identity.name)}; via = MessageHelper.canonicalAddress(identity.email); } @@ -2162,7 +2163,7 @@ public class FragmentCompose extends FragmentBase { String email = MessageHelper.canonicalAddress(identity.email); if (email.equals(from)) { draft.identity = identity.id; - draft.from = new InternetAddress[]{new InternetAddress(identity.email, identity.name)}; + draft.from = new InternetAddressImpl[]{new InternetAddressImpl(identity.email, identity.name)}; break; } if (identity.account.equals(draft.account)) { @@ -2183,10 +2184,10 @@ public class FragmentCompose extends FragmentBase { if (draft.identity == null) { if (primary != null) { draft.identity = primary.id; - draft.from = new InternetAddress[]{new InternetAddress(primary.email, primary.name)}; + draft.from = new InternetAddressImpl[]{new InternetAddressImpl(primary.email, primary.name)}; } else if (first != null && icount == 1) { draft.identity = first.id; - draft.from = new InternetAddress[]{new InternetAddress(first.email, first.name)}; + draft.from = new InternetAddressImpl[]{new InternetAddressImpl(first.email, first.name)}; } } @@ -2557,11 +2558,11 @@ public class FragmentCompose extends FragmentBase { List attachments = db.attachment().getAttachments(draft.id); // Get data - InternetAddress afrom[] = (identity == null ? null : new InternetAddress[]{new InternetAddress(identity.email, identity.name)}); + InternetAddressImpl afrom[] = (identity == null ? null : new InternetAddressImpl[]{new InternetAddressImpl(identity.email, identity.name)}); - InternetAddress ato[] = null; - InternetAddress acc[] = null; - InternetAddress abcc[] = null; + InternetAddress[] ato = null; + InternetAddress[] acc = null; + InternetAddress[] abcc = null; boolean lookup_mx = prefs.getBoolean("lookup_mx", false); diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 356a33b8fa..e336cfb3ea 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -72,6 +72,8 @@ import androidx.preference.PreferenceManager; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.sun.mail.iap.ConnectionException; import com.sun.mail.util.FolderClosedIOException; +import org.json.JSONException; +import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.File; @@ -876,4 +878,18 @@ public class Helper { bundle.writeToParcel(p, 0); return p.dataSize(); } + + public static int computeAddressHashcode(JSONObject jsonObject) { + int ia = 0; + int ip = 0; + String address = null; + try { + ia = jsonObject.getString("address").hashCode(); + if (jsonObject.has("personal")) { + ip = jsonObject.getString("personal").hashCode(); + } + } catch (JSONException ignored) { } + return ia*2 + ip*8; + } + } diff --git a/app/src/main/java/eu/faircode/email/Log.java b/app/src/main/java/eu/faircode/email/Log.java index 30706fc449..2b92e9b945 100644 --- a/app/src/main/java/eu/faircode/email/Log.java +++ b/app/src/main/java/eu/faircode/email/Log.java @@ -80,7 +80,7 @@ import java.util.concurrent.TimeoutException; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.Part; -import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; public class Log { private static final String TAG = "fairemail"; @@ -750,7 +750,7 @@ public class Log { return (int) (getFreeMem() / 1024L / 1024L); } - static InternetAddress myAddress() throws UnsupportedEncodingException { - return new InternetAddress("marcel+fairemail@faircode.eu", "FairCode"); + static InternetAddressImpl myAddress() throws UnsupportedEncodingException { + return new InternetAddressImpl("marcel+fairemail@faircode.eu", "FairCode"); } } \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 0a1955bd64..a0936652b5 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -67,6 +67,7 @@ import javax.mail.Session; import javax.mail.internet.AddressException; import javax.mail.internet.ContentType; import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; import javax.mail.internet.MailDateFormat; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; @@ -553,7 +554,7 @@ public class MessageHelper { try { MailTo mailto = MailTo.parse(to.substring(lt + 1, gt)); if (mailto.getTo() != null) - return new Address[]{new InternetAddress(mailto.getTo().split(",")[0])}; + return new Address[]{new InternetAddressImpl(mailto.getTo().split(",")[0])}; } catch (android.net.ParseException ex) { Log.i(ex); } @@ -1138,9 +1139,14 @@ public class MessageHelper { if (a1.length != a2.length) return false; - for (int i = 0; i < a1.length; i++) - if (!a1[i].toString().equals(a2[i].toString())) + Address address1; + Address address2; + for (int i = 0; i < a1.length; i++) { + address1 = a1[i]; + address2 = a2[i]; + if (!address1.equals(address2)) return false; + } return true; } diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 1ba2625b23..f38b80f252 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -55,7 +55,7 @@ import javax.mail.MessageRemovedException; import javax.mail.MessagingException; import javax.mail.SendFailedException; import javax.mail.Session; -import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; import javax.mail.internet.MimeMessage; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; @@ -326,7 +326,7 @@ public class ServiceSend extends ServiceBase { // Add reply to if (ident.replyto != null) - imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)}); + imessage.setReplyTo(new Address[]{new InternetAddressImpl(ident.replyto)}); // Add bcc if (ident.bcc != null) { @@ -334,7 +334,7 @@ public class ServiceSend extends ServiceBase { Address[] existing = imessage.getRecipients(Message.RecipientType.BCC); if (existing != null) bcc.addAll(Arrays.asList(existing)); - bcc.add(new InternetAddress(ident.bcc)); + bcc.add(new InternetAddressImpl(ident.bcc)); imessage.setRecipients(Message.RecipientType.BCC, bcc.toArray(new Address[0])); } diff --git a/app/src/main/java/eu/faircode/email/TupleMessageEx.java b/app/src/main/java/eu/faircode/email/TupleMessageEx.java index d9929e84df..c312bb1f49 100644 --- a/app/src/main/java/eu/faircode/email/TupleMessageEx.java +++ b/app/src/main/java/eu/faircode/email/TupleMessageEx.java @@ -20,10 +20,16 @@ package eu.faircode.email; */ import androidx.room.Ignore; +import org.json.JSONAddress; +import org.json.JSONException; +import org.json.JSONObject; +import java.io.UnsupportedEncodingException; import java.util.Objects; import javax.mail.Address; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetAddressImpl; public class TupleMessageEx extends EntityMessage { public String accountName; @@ -47,6 +53,11 @@ public class TupleMessageEx extends EntityMessage { @Ignore public boolean duplicate; + @Ignore + public boolean calculatedVia = false; + @Ignore + public Address via = null; + @Override public boolean equals(Object obj) { if (obj instanceof TupleMessageEx) { @@ -72,4 +83,32 @@ public class TupleMessageEx extends EntityMessage { } return false; } + + public Address getVia() { + if (!calculatedVia && identityEmail != null) { + JSONObject jaddress = new JSONObject(); + JSONAddress key = null; + try { + jaddress.put("address", identityEmail); + jaddress.put("personal", identityName); + key = new JSONAddress(jaddress); + via = DB.addressCache.get(key); + } catch (JSONException e) { + } + if (via == null) { + try { + via = new InternetAddressImpl(identityEmail, identityName); + if (key != null) { + synchronized (DB.addressCache) { + DB.inverseAddressCache.put(via, key); + DB.addressCache.put(key, via); + } + } + } catch (UnsupportedEncodingException ignored) { + } + } + calculatedVia = true; + } + return via; + } } diff --git a/app/src/main/java/javax/mail/internet/InternetAddressImpl.java b/app/src/main/java/javax/mail/internet/InternetAddressImpl.java new file mode 100644 index 0000000000..11d8306fab --- /dev/null +++ b/app/src/main/java/javax/mail/internet/InternetAddressImpl.java @@ -0,0 +1,51 @@ +package javax.mail.internet; + +import javax.mail.Address; +import java.io.UnsupportedEncodingException; +import java.util.Objects; + +public class InternetAddressImpl extends InternetAddress { + public InternetAddressImpl() { + super(); + } + + public InternetAddressImpl(String address) throws AddressException { + super(address); + } + + public InternetAddressImpl(String address, boolean strict) throws AddressException { + super(address, strict); + } + + public InternetAddressImpl(String address, String personal) throws UnsupportedEncodingException { + super(address, personal); + } + + public InternetAddressImpl(String address, String personal, String charset) throws UnsupportedEncodingException { + super(address, personal, charset); + } + + public InternetAddressImpl(InternetAddress address) throws UnsupportedEncodingException { + setAddress(address.address); + setPersonal(address.personal); + } + + @Override + public boolean equals(Object a) { + if (!super.equals(a)) return false; + + InternetAddressImpl address1 = this; + InternetAddress address2 = (InternetAddress) a; // super.equals checked for a instanceof InternetAddress + // super.equals already checked this.address for equality + return Objects.equals(address1.getPersonal(), address2.getPersonal()); + } + + @Override + public int hashCode() { + String personal = this.getPersonal(); + int personalHash = 0; + if (personal != null) + personalHash = personal.hashCode(); + return super.hashCode() + personalHash*2; + } +} diff --git a/app/src/main/java/org/json/JSONAddress.java b/app/src/main/java/org/json/JSONAddress.java new file mode 100644 index 0000000000..e3f3ccc00a --- /dev/null +++ b/app/src/main/java/org/json/JSONAddress.java @@ -0,0 +1,22 @@ +package org.json; + +import eu.faircode.email.Helper; + +public class JSONAddress extends JSONObject { + + public final JSONObject jsonObject; + + public JSONAddress(JSONObject jsonObject) { + this.jsonObject = jsonObject; + } + + @Override + public int hashCode() { + return Helper.computeAddressHashcode(jsonObject); + } + + @Override + public boolean equals(Object obj) { + return obj != null && this.hashCode() == obj.hashCode(); + } +}