diff --git a/FAQ.md b/FAQ.md
index 101bb3fb9e..5d72bd2b36 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -334,7 +334,6 @@ Fonts, sizes, colors, etc should be material design whenever possible.
* [(177) What does 'Sensitivity' mean?](#user-content-faq177)
* [(178) Why are widgets not updating?](#user-content-faq178)
* [(179) What are reply templates?](#user-content-faq179)
-* [(180) Can you add support for Gravatar / Libravatar?](#user-content-faq180)
[I have another question.](#user-content-get-support)
@@ -4689,15 +4688,6 @@ Templates can have the following options:
-
-**(180) Can you add support for Gravatar / Libravatar?**
-
-FairEmail supported both Gravatar and Libravatar until version 1.1883,
-but [Google forced removal](https://forum.xda-developers.com/t/app-5-0-fairemail-fully-featured-open-source-privacy-oriented-email-app.3824168/post-86800867) of these features,
-even though they were never present in the Play store version of the app.
-
-
-
Get support
🌎 [Google Translate](https://translate.google.com/translate?sl=en&u=https://github.com/M66B/FairEmail/blob/master/FAQ.md%23user-content-get-support)
diff --git a/app/build.gradle b/app/build.gradle
index 2014b5d494..15bfebb25d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -48,16 +48,16 @@ android {
sourceSets {
github {
- java.srcDirs = ['src/main/java', 'src/play/java']
+ java.srcDirs = ['src/main/java', 'src/play/java', 'src/extra/java']
}
fdroid {
- java.srcDirs = ['src/main/java', 'src/fdroid/java']
+ java.srcDirs = ['src/main/java', 'src/fdroid/java', 'src/extra/java']
}
play {
- java.srcDirs = ['src/main/java', 'src/play/java']
+ java.srcDirs = ['src/main/java', 'src/play/java', 'src/dummy/java']
}
amazon {
- java.srcDirs = ['src/main/java', 'src/amazon/java']
+ java.srcDirs = ['src/main/java', 'src/amazon/java', 'src/extra/java']
}
main.res.srcDirs += 'src/main/resExtra'
}
diff --git a/app/src/dummy/java/eu/faircode/email/Extra.java b/app/src/dummy/java/eu/faircode/email/Extra.java
new file mode 100644
index 0000000000..67ff455d1f
--- /dev/null
+++ b/app/src/dummy/java/eu/faircode/email/Extra.java
@@ -0,0 +1,45 @@
+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-2022 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Context;
+import android.os.OperationCanceledException;
+
+import java.util.concurrent.Callable;
+
+public class Extra {
+ static Callable getG(String email, int scaleToPixels, Context context) {
+ return new Callable() {
+ @Override
+ public ContactInfo.Favicon call() throws Exception {
+ throw new OperationCanceledException();
+ }
+ };
+ }
+
+ static Callable getL(String email, int scaleToPixels, Context context) {
+ return new Callable() {
+ @Override
+ public ContactInfo.Favicon call() throws Exception {
+ throw new OperationCanceledException();
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/app/src/extra/java/eu/faircode/email/Extra.java b/app/src/extra/java/eu/faircode/email/Extra.java
new file mode 100644
index 0000000000..ac669af3e0
--- /dev/null
+++ b/app/src/extra/java/eu/faircode/email/Extra.java
@@ -0,0 +1,117 @@
+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-2021 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.Callable;
+
+public class Extra {
+ private static final String GRAVATAR_URI = "https://www.gravatar.com/avatar/";
+ private static final int GRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
+ private static final int GRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
+ private static final int LIBRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
+ private static final int LIBRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
+ private static final String LIBRAVATAR_DNS = "_avatars-sec._tcp,_avatars._tcp";
+ private static final String LIBRAVATAR_URI = "https://seccdn.libravatar.org/avatar/";
+
+ static Callable getG(String email, int scaleToPixels, Context context) {
+ return new Callable() {
+ @Override
+ public ContactInfo.Favicon call() throws Exception {
+ String hash = Helper.md5(email.getBytes());
+ URL url = new URL(GRAVATAR_URI + hash + "?d=404");
+ Log.i("Gravatar key=" + email + " url=" + url);
+
+ HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setRequestMethod("GET");
+ urlConnection.setReadTimeout(GRAVATAR_READ_TIMEOUT);
+ urlConnection.setConnectTimeout(GRAVATAR_CONNECT_TIMEOUT);
+ urlConnection.setRequestProperty("User-Agent", WebViewEx.getUserAgent(context));
+ urlConnection.connect();
+
+ try {
+ int status = urlConnection.getResponseCode();
+ if (status == HttpURLConnection.HTTP_OK) {
+ // Positive reply
+ Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
+ return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "extra", false));
+ } else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
+ // Negative reply
+ return null;
+ } else
+ throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
+ } finally {
+ urlConnection.disconnect();
+ }
+ }
+ };
+ }
+
+ static Callable getL(String email, int scaleToPixels, Context context) {
+ return new Callable() {
+ @Override
+ public ContactInfo.Favicon call() throws Exception {
+ String domain = UriHelper.getEmailDomain(email);
+
+ // https://wiki.libravatar.org/api/
+ String baseUrl = LIBRAVATAR_URI;
+ for (String dns : 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(LIBRAVATAR_READ_TIMEOUT);
+ urlConnection.setConnectTimeout(LIBRAVATAR_CONNECT_TIMEOUT);
+ urlConnection.setRequestProperty("User-Agent", WebViewEx.getUserAgent(context));
+ urlConnection.connect();
+
+ try {
+ int status = urlConnection.getResponseCode();
+ if (status == HttpURLConnection.HTTP_OK) {
+ // Positive reply
+ Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
+ return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "extra", false));
+ } else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
+ // Negative reply
+ return null;
+ } else
+ throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
+ } finally {
+ urlConnection.disconnect();
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java
index 1bd03fb0e4..8da7cd4aa1 100644
--- a/app/src/main/java/eu/faircode/email/AdapterMessage.java
+++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java
@@ -6900,6 +6900,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0) {
@@ -451,7 +462,7 @@ public class ContactInfo {
// Add to cache
File output = new File(dir,
- domain +
+ ("extra".equals(info.type) ? email : domain) +
"." + info.type +
(info.verified ? "_verified" : ""));
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(output))) {
@@ -966,7 +977,7 @@ public class ContactInfo {
String displayName;
}
- private static class Favicon {
+ static class Favicon {
private Bitmap bitmap;
private String type;
private boolean verified;
@@ -977,7 +988,7 @@ public class ContactInfo {
this.source = source;
}
- private Favicon(@NonNull Bitmap bitmap, String type, boolean verified) {
+ Favicon(@NonNull Bitmap bitmap, String type, boolean verified) {
this.bitmap = bitmap;
this.type = type;
this.verified = verified;
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
index 8771dd4971..7bfb97b954 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
@@ -107,6 +107,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private TextView tvBimiHint;
private TextView tvBimiUnverified;
private SwitchCompat swBimi;
+ private SwitchCompat swEFavicons;
private SwitchCompat swFavicons;
private SwitchCompat swFaviconsPartial;
private TextView tvFaviconsHint;
@@ -187,7 +188,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
"nav_options", "nav_categories", "nav_count", "nav_unseen_drafts", "nav_count_pinned", "navbar_colorize",
"threading", "threading_unread", "indentation", "seekbar", "actionbar", "actionbar_color", "group_category",
"highlight_unread", "highlight_color", "color_stripe", "color_stripe_wide",
- "avatars", "bimi", "favicons", "favicons_partial", "generated_icons", "identicons",
+ "avatars", "bimi", "efavicons", "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",
@@ -257,6 +258,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
tvBimiHint = view.findViewById(R.id.tvBimiHint);
tvBimiUnverified = view.findViewById(R.id.tvBimiUnverified);
ibBimi = view.findViewById(R.id.ibBimi);
+ swEFavicons = view.findViewById(R.id.swEFavicons);
swFavicons = view.findViewById(R.id.swFavicons);
swFaviconsPartial = view.findViewById(R.id.swFaviconsPartial);
tvFaviconsHint = view.findViewById(R.id.tvFaviconsHint);
@@ -713,6 +715,15 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
}
});
+ swEFavicons.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
+ swEFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ prefs.edit().putBoolean("efavicons", checked).apply();
+ ContactInfo.clearCache(compoundButton.getContext());
+ }
+ });
+
swFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -1315,6 +1326,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
//swColorStripeWide.setEnabled(swColorStripe.isChecked());
swAvatars.setChecked(prefs.getBoolean("avatars", true));
swBimi.setChecked(prefs.getBoolean("bimi", false));
+ swEFavicons.setChecked(prefs.getBoolean("efavicons", 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 6ada786d4f..f13c454a92 100644
--- a/app/src/main/res/layout/fragment_options_display.xml
+++ b/app/src/main/res/layout/fragment_options_display.xml
@@ -828,6 +828,17 @@
app:layout_constraintTop_toBottomOf="@+id/tvBimiVerified"
app:srcCompat="@drawable/twotone_info_24" />
+
+
Show Brand Indicators for Message Identification (BIMI)
Unverified sender
Verified sender
+ Show extravatars
Show favicons
Scan only the first %1$s of the web page
Show generated icons