From fb52254c7378e06d065cebcb706892a874dcb792 Mon Sep 17 00:00:00 2001 From: M66B Date: Tue, 8 Mar 2022 10:20:57 +0100 Subject: [PATCH] Added Libravatar support --- FAQ.md | 2 +- app/build.gradle | 12 ++++ .../java/eu/faircode/email/ContactInfo.java | 56 ++++++++++++++++++- .../email/FragmentOptionsDisplay.java | 22 +++++++- .../res/layout/fragment_options_display.xml | 28 +++++++++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 113 insertions(+), 8 deletions(-) diff --git a/FAQ.md b/FAQ.md index bb7529b79f..02bc9cc463 100644 --- a/FAQ.md +++ b/FAQ.md @@ -4492,7 +4492,7 @@ Related questions: 🌎 [Google Translate](https://translate.google.com/translate?sl=en&u=https://github.com/M66B/FairEmail/blob/master/FAQ.md%23user-content-faq173) * The Play store version does not support Android Auto, see [this FAQ](#user-content-faq165) for more information -* The Play store version does not support [Gravatars](https://gravatar.com/), see [here](https://forum.xda-developers.com/t/app-5-0-fairemail-fully-featured-open-source-privacy-oriented-email-app.3824168/post-85226179) for the reason +* The Play store version does not support [Gravatars](https://gravatar.com/) and [Libravatars](https://www.libravatar.org/), see [here](https://forum.xda-developers.com/t/app-5-0-fairemail-fully-featured-open-source-privacy-oriented-email-app.3824168/post-85226179) for the reason * The Play store version does not support Amazon devices with Android 5 Lollipop because there are critical bugs in this Android version of Amazon * The GitHub version will check for [updates on GitHub](https://github.com/M66B/FairEmail/releases) and is updated more frequently * The GitHub version has some different links, some more options (like sharing the HTML of a message) and some different default values (more geared to advanced users) diff --git a/app/build.gradle b/app/build.gradle index 5585c99ccd..e82563ce2b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -158,6 +158,9 @@ android { buildConfigField "String", "GITHUB_LATEST_API", "\"https://api.github.com/repos/M66B/FairEmail/releases/latest\"" buildConfigField "String", "GITHUB_LATEST_URI", "\"https://github.com/M66B/FairEmail/releases\"" buildConfigField "String", "GRAVATAR_URI", "\"https://www.gravatar.com/avatar/\"" + buildConfigField "String", "LIBRAVATAR_DNS", "\"_avatars-sec._tcp,_avatars._tcp\"" + buildConfigField "String", "LIBRAVATAR_URI", "\"https://seccdn.libravatar.org/avatar/\"" + buildConfigField "String", "LIBRAVATAR_INFO", "\"https://libravatar.org/\"" buildConfigField "String", "GPA_URI", localProperties.getProperty("gpa.uri", "\"\"") buildConfigField "String", "PAYPAL_URI", localProperties.getProperty("paypal.uri", "\"\"") } @@ -172,6 +175,9 @@ android { buildConfigField "String", "GITHUB_LATEST_API", "\"https://api.github.com/repos/M66B/FairEmail/releases/latest\"" buildConfigField "String", "GITHUB_LATEST_URI", "\"https://github.com/M66B/FairEmail/releases\"" buildConfigField "String", "GRAVATAR_URI", "\"https://www.gravatar.com/avatar/\"" + buildConfigField "String", "LIBRAVATAR_DNS", "\"_avatars-sec._tcp,_avatars._tcp\"" + buildConfigField "String", "LIBRAVATAR_URI", "\"https://seccdn.libravatar.org/avatar/\"" + buildConfigField "String", "LIBRAVATAR_INFO", "\"https://libravatar.org/\"" buildConfigField "String", "PAYPAL_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\"" } @@ -187,6 +193,9 @@ android { buildConfigField "String", "GITHUB_LATEST_API", "\"\"" buildConfigField "String", "GITHUB_LATEST_URI", "\"\"" buildConfigField "String", "GRAVATAR_URI", "\"\"" + buildConfigField "String", "LIBRAVATAR_DNS", "\"\"" + buildConfigField "String", "LIBRAVATAR_URI", "\"\"" + buildConfigField "String", "LIBRAVATAR_INFO", "\"\"" buildConfigField "String", "PAYPAL_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\"" } @@ -202,6 +211,9 @@ android { buildConfigField "String", "GITHUB_LATEST_API", "\"\"" buildConfigField "String", "GITHUB_LATEST_URI", "\"\"" buildConfigField "String", "GRAVATAR_URI", "\"\"" + buildConfigField "String", "LIBRAVATAR_DNS", "\"\"" + buildConfigField "String", "LIBRAVATAR_URI", "\"\"" + buildConfigField "String", "LIBRAVATAR_INFO", "\"\"" buildConfigField "String", "PAYPAL_URI", "\"\"" buildConfigField "String", "GPA_URI", "\"\"" } diff --git a/app/src/main/java/eu/faircode/email/ContactInfo.java b/app/src/main/java/eu/faircode/email/ContactInfo.java index 3ec17fd7d4..535ae75736 100644 --- a/app/src/main/java/eu/faircode/email/ContactInfo.java +++ b/app/src/main/java/eu/faircode/email/ContactInfo.java @@ -90,7 +90,7 @@ import javax.net.ssl.SSLSession; public class ContactInfo { private String email; private Bitmap bitmap; - private String type; + private String type; // contact, vmc, gravatar, libravatar, favicon, identicon, letter, unknown private boolean verified; private String displayName; private Uri lookupUri; @@ -159,6 +159,10 @@ public class ContactInfo { return lookupUri; } + boolean isEmailBased() { + return ("gravatar".equals(type) || "libravatar".equals(type)); + } + boolean isKnown() { return known; } @@ -258,6 +262,7 @@ public class ContactInfo { boolean avatars = prefs.getBoolean("avatars", true); boolean bimi = prefs.getBoolean("bimi", false); boolean gravatars = prefs.getBoolean("gravatars", false); + boolean libravatars = prefs.getBoolean("libravatars", false); boolean favicons = prefs.getBoolean("favicons", false); boolean generated = prefs.getBoolean("generated_icons", true); boolean identicons = prefs.getBoolean("identicons", false); @@ -308,7 +313,7 @@ public class ContactInfo { // Favicon if (info.bitmap == null && !EntityFolder.JUNK.equals(folderType) && - (bimi || (gravatars && canGravatars()) || favicons)) { + (bimi || (canGravatars() && (gravatars || libravatars)) || favicons)) { String d = UriHelper.getEmailDomain(info.email); if (d != null) { // Prevent using Doodles @@ -334,6 +339,8 @@ public class ContactInfo { File[] files = null; if (gravatars && canGravatars()) { File f = new File(dir, email + ".gravatar"); + if (!f.exists()) + f = new File(dir, email + ".libravatar"); if (f.exists()) files = new File[]{f}; } @@ -404,6 +411,49 @@ public class ContactInfo { } })); + if (libravatars && canGravatars()) + futures.add(executorFavicon.submit(new Callable() { + @Override + public Favicon call() throws Exception { + // https://wiki.libravatar.org/api/ + String baseUrl = BuildConfig.LIBRAVATAR_URI; + for (String dns : BuildConfig.LIBRAVATAR_DNS.split(",")) { + DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, dns + "." + domain, "srv"); + if (records.length > 0) { + baseUrl = (records[0].port == 443 ? "https" : "http") + "://" + records[0].name + "/avatar/"; + break; + } + } + + String hash = Helper.md5(email.getBytes()); + + URL url = new URL(baseUrl + hash + "?d=404"); + Log.i("Libravatar key=" + email + " url=" + url); + + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setRequestMethod("GET"); + urlConnection.setReadTimeout(GRAVATAR_TIMEOUT); + urlConnection.setConnectTimeout(GRAVATAR_TIMEOUT); + urlConnection.setRequestProperty("User-Agent", WebViewEx.getUserAgent(context)); + urlConnection.connect(); + + try { + int status = urlConnection.getResponseCode(); + if (status == HttpURLConnection.HTTP_OK) { + // Positive reply + Bitmap bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream()); + return (bitmap == null ? null : new Favicon(bitmap, "libravatar", false)); + } else if (status == HttpURLConnection.HTTP_NOT_FOUND) { + // Negative reply + return null; + } else + throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage()); + } finally { + urlConnection.disconnect(); + } + } + })); + if (favicons) { String host = domain; while (host.indexOf('.') > 0) { @@ -480,7 +530,7 @@ public class ContactInfo { // Add to cache File output = new File(dir, - ("gravatar".equals(info.type) ? email : domain) + + (info.isEmailBased() ? email : domain) + "." + info.type + (info.verified ? "_verified" : "")); try (OutputStream os = new BufferedOutputStream(new FileOutputStream(output))) { diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java index 129a404f03..c874ee39d0 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java @@ -106,6 +106,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer private SwitchCompat swBimi; private SwitchCompat swGravatars; private TextView tvGravatarsHint; + private SwitchCompat swLibravatars; + private ImageButton ibLibravatars; private SwitchCompat swFavicons; private SwitchCompat swFaviconsPartial; private TextView tvFaviconsHint; @@ -184,7 +186,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer "nav_options", "nav_count", "nav_unseen_drafts", "nav_count_pinned", "navbar_colorize", "threading", "threading_unread", "indentation", "seekbar", "actionbar", "actionbar_color", "highlight_unread", "highlight_color", "color_stripe", "color_stripe_wide", - "avatars", "bimi", "gravatars", "favicons", "favicons_partial", "generated_icons", "identicons", + "avatars", "bimi", "gravatars", "libravatars", "favicons", "favicons_partial", "generated_icons", "identicons", "circular", "saturation", "brightness", "threshold", "email_format", "prefer_contact", "only_contact", "distinguish_contacts", "show_recipients", "font_size_sender", "sender_ellipsize", @@ -254,6 +256,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer ibBimi = view.findViewById(R.id.ibBimi); swGravatars = view.findViewById(R.id.swGravatars); tvGravatarsHint = view.findViewById(R.id.tvGravatarsHint); + swLibravatars = view.findViewById(R.id.swLibravatars); + ibLibravatars = view.findViewById(R.id.ibLibravatars); swFavicons = view.findViewById(R.id.swFavicons); swFaviconsPartial = view.findViewById(R.id.swFaviconsPartial); tvFaviconsHint = view.findViewById(R.id.tvFaviconsHint); @@ -712,6 +716,21 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer } }); + swLibravatars.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("libravatars", checked).apply(); + ContactInfo.clearCache(compoundButton.getContext()); + } + }); + + ibLibravatars.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Helper.view(v.getContext(), Uri.parse(BuildConfig.LIBRAVATAR_INFO), true); + } + }); + swFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -1302,6 +1321,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swAvatars.setChecked(prefs.getBoolean("avatars", true)); swBimi.setChecked(prefs.getBoolean("bimi", false)); swGravatars.setChecked(prefs.getBoolean("gravatars", false)); + swLibravatars.setChecked(prefs.getBoolean("libravatars", false)); swFavicons.setChecked(prefs.getBoolean("favicons", false)); swFaviconsPartial.setChecked(prefs.getBoolean("favicons_partial", true)); swFaviconsPartial.setEnabled(swFavicons.isChecked()); diff --git a/app/src/main/res/layout/fragment_options_display.xml b/app/src/main/res/layout/fragment_options_display.xml index c99e602542..d71d44302e 100644 --- a/app/src/main/res/layout/fragment_options_display.xml +++ b/app/src/main/res/layout/fragment_options_display.xml @@ -843,6 +843,28 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swGravatars" /> + + + + + app:constraint_referenced_ids="swGravatars,tvGravatarsHint,swLibravatars,ibLibravatars" /> @@ -1873,7 +1895,7 @@ app:layout_constraintTop_toBottomOf="@id/swAuthenticationIndicator" /> Use wide color stripe Show contact photos Show Gravatars + Show Libravatars Show Brand Indicators for Message Identification (BIMI) Unverified sender Verified sender