diff --git a/app/src/dummy/java/eu/faircode/email/Extra.java b/app/src/dummy/java/eu/faircode/email/Extra.java
deleted file mode 100644
index 67ff455d1f..0000000000
--- a/app/src/dummy/java/eu/faircode/email/Extra.java
+++ /dev/null
@@ -1,45 +0,0 @@
-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
deleted file mode 100644
index ac669af3e0..0000000000
--- a/app/src/extra/java/eu/faircode/email/Extra.java
+++ /dev/null
@@ -1,117 +0,0 @@
-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 8d53023f05..ab14191593 100644
--- a/app/src/main/java/eu/faircode/email/AdapterMessage.java
+++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java
@@ -6880,9 +6880,10 @@ public class AdapterMessage extends RecyclerView.Adapter REL_EXCLUDE = Collections.unmodifiableList(Arrays.asList(
@@ -133,6 +141,10 @@ public class ContactInfo {
return type;
}
+ boolean isEmailBased() {
+ return ("gravatar".equals(type) || "libravatar".equals(type));
+ }
+
boolean isVerified() {
return (bitmap != null && verified);
}
@@ -254,9 +266,10 @@ public class ContactInfo {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean avatars = prefs.getBoolean("avatars", true);
- boolean bimi = (prefs.getBoolean("bimi", false) && !BuildConfig.PLAY_STORE_RELEASE);
- boolean efavicons = (prefs.getBoolean("efavicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
- boolean favicons = (prefs.getBoolean("favicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
+ 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);
boolean circular = prefs.getBoolean("circular", true);
@@ -305,7 +318,7 @@ public class ContactInfo {
// Favicon
if (info.bitmap == null &&
- !EntityFolder.JUNK.equals(folderType) && (bimi || efavicons || favicons)) {
+ !EntityFolder.JUNK.equals(folderType) && (bimi || gravatars || libravatars || favicons)) {
String d = UriHelper.getEmailDomain(info.email);
if (d != null) {
// Prevent using Doodles
@@ -331,8 +344,13 @@ public class ContactInfo {
try {
// check cache
File[] files = null;
- if (efavicons) {
- File f = new File(dir, email + ".extra");
+ if (gravatars) {
+ File f = new File(dir, email + ".gravatar");
+ if (f.exists())
+ files = new File[]{f};
+ }
+ if (files == null && libravatars) {
+ File f = new File(dir, email + ".libravatar");
if (f.exists())
files = new File[]{f};
}
@@ -371,10 +389,10 @@ public class ContactInfo {
}
}));
- if (efavicons) {
- futures.add(executorFavicon.submit(Extra.getG(email, scaleToPixels, context)));
- futures.add(executorFavicon.submit(Extra.getL(email, scaleToPixels, context)));
- }
+ if (gravatars)
+ futures.add(executorFavicon.submit(getGravatar(email, scaleToPixels, context)));
+ if (libravatars)
+ futures.add(executorFavicon.submit(getLibravatar(email, scaleToPixels, context)));
if (favicons) {
String host = domain;
@@ -464,7 +482,7 @@ public class ContactInfo {
// Add to cache
File output = new File(dir,
- ("extra".equals(info.type) ? email : domain) +
+ (info.isEmailBased() ? email : domain) +
"." + info.type +
(info.verified ? "_verified" : ""));
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(output))) {
@@ -860,6 +878,85 @@ public class ContactInfo {
}
}
+ static Callable getGravatar(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, "gravatar", 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 getLibravatar(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, "libravatar", false));
+ } else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
+ // Negative reply
+ return null;
+ } else
+ throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
+ } finally {
+ urlConnection.disconnect();
+ }
+ }
+ };
+ }
+
private static boolean isRecoverable(Throwable ex, Context context) {
if (ex instanceof SocketTimeoutException) {
ConnectivityManager cm = Helper.getSystemService(context, ConnectivityManager.class);
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
index ea183d8533..9de7e54a13 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java
@@ -108,7 +108,10 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private TextView tvBimiHint;
private TextView tvBimiUnverified;
private SwitchCompat swBimi;
- private SwitchCompat swEFavicons;
+ private SwitchCompat swGravatars;
+ private TextView tvGravatarPrivacy;
+ private SwitchCompat swLibravatars;
+ private TextView tvLibravatarPrivacy;
private SwitchCompat swFavicons;
private SwitchCompat swFaviconsPartial;
private TextView tvFaviconsHint;
@@ -177,7 +180,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private SwitchCompat swAuthentication;
private SwitchCompat swAuthenticationIndicator;
- private Group grpPlay;
private Group grpUnzip;
private NumberFormat NF = NumberFormat.getNumberInstance();
@@ -190,7 +192,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",
"highlight_unread", "highlight_color", "color_stripe", "color_stripe_wide",
- "avatars", "bimi", "efavicons", "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",
@@ -262,7 +264,10 @@ 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);
+ swGravatars = view.findViewById(R.id.swGravatars);
+ tvGravatarPrivacy = view.findViewById(R.id.tvGravatarPrivacy);
+ swLibravatars = view.findViewById(R.id.swLibravatars);
+ tvLibravatarPrivacy = view.findViewById(R.id.tvLibravatarPrivacy);
swFavicons = view.findViewById(R.id.swFavicons);
swFaviconsPartial = view.findViewById(R.id.swFaviconsPartial);
tvFaviconsHint = view.findViewById(R.id.tvFaviconsHint);
@@ -329,7 +334,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swAuthentication = view.findViewById(R.id.swAuthentication);
swAuthenticationIndicator = view.findViewById(R.id.swAuthenticationIndicator);
- grpPlay = view.findViewById(R.id.grpPlay);
grpUnzip = view.findViewById(R.id.grpUnzip);
List fonts = StyleHelper.getFonts(getContext());
@@ -728,14 +732,38 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
}
});
- swEFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ swGravatars.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
- prefs.edit().putBoolean("efavicons", checked).apply();
+ prefs.edit().putBoolean("gravatars", checked).apply();
ContactInfo.clearCache(compoundButton.getContext());
}
});
+ tvGravatarPrivacy.getPaint().setUnderlineText(true);
+ tvGravatarPrivacy.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.view(v.getContext(), Uri.parse(Helper.GRAVATAR_PRIVACY_URI), true);
+ }
+ });
+
+ swLibravatars.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ prefs.edit().putBoolean("libravatars", checked).apply();
+ ContactInfo.clearCache(compoundButton.getContext());
+ }
+ });
+
+ tvLibravatarPrivacy.getPaint().setUnderlineText(true);
+ tvLibravatarPrivacy.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.view(v.getContext(), Uri.parse(Helper.LIBRAVATAR_PRIVACY_URI), true);
+ }
+ });
+
swFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -1227,7 +1255,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
// Initialize
FragmentDialogTheme.setBackground(getContext(), view, false);
- grpPlay.setVisibility(BuildConfig.PLAY_STORE_RELEASE || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
swFaviconsPartial.setText(getString(R.string.title_advanced_favicons_partial,
Helper.humanReadableByteCount(ContactInfo.FAVICON_READ_BYTES, false)));
grpUnzip.setVisibility(Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? View.GONE : View.VISIBLE);
@@ -1338,12 +1365,10 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swColorStripeWide.setChecked(prefs.getBoolean("color_stripe_wide", false));
//swColorStripeWide.setEnabled(swColorStripe.isChecked());
swAvatars.setChecked(prefs.getBoolean("avatars", true));
- swBimi.setChecked(prefs.getBoolean("bimi", false) && !BuildConfig.PLAY_STORE_RELEASE);
- swBimi.setEnabled(!BuildConfig.PLAY_STORE_RELEASE);
- swEFavicons.setChecked(prefs.getBoolean("efavicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
- swEFavicons.setEnabled(!BuildConfig.PLAY_STORE_RELEASE);
- swFavicons.setChecked(prefs.getBoolean("favicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
- swFavicons.setEnabled(!BuildConfig.PLAY_STORE_RELEASE);
+ 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());
swGeneratedIcons.setChecked(prefs.getBoolean("generated_icons", true));
diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java
index 59ef5aa90b..23d316a29d 100644
--- a/app/src/main/java/eu/faircode/email/Helper.java
+++ b/app/src/main/java/eu/faircode/email/Helper.java
@@ -184,6 +184,8 @@ public class Helper {
static final String SUPPORT_URI = "https://contact.faircode.eu/";
static final String TEST_URI = "https://play.google.com/apps/testing/" + BuildConfig.APPLICATION_ID;
static final String BIMI_PRIVACY_URI = "https://datatracker.ietf.org/doc/html/draft-brotman-ietf-bimi-guidance-03#section-7.4";
+ static final String GRAVATAR_PRIVACY_URI = "https://automattic.com/privacy/";
+ static final String LIBRAVATAR_PRIVACY_URI = "https://www.libravatar.org/privacy/";
static final String ID_COMMAND_URI = "https://datatracker.ietf.org/doc/html/rfc2971#section-3.1";
static final String AUTH_RESULTS_URI = "https://datatracker.ietf.org/doc/html/rfc7601";
static final String FAVICON_PRIVACY_URI = "https://en.wikipedia.org/wiki/Favicon";
diff --git a/app/src/main/res/layout/fragment_options_display.xml b/app/src/main/res/layout/fragment_options_display.xml
index 2f5f18fdf2..d7cf8d452d 100644
--- a/app/src/main/res/layout/fragment_options_display.xml
+++ b/app/src/main/res/layout/fragment_options_display.xml
@@ -808,23 +808,6 @@
app:layout_constraintTop_toBottomOf="@id/vSeparatorAvatar"
app:switchPadding="12dp" />
-
-
-
-
@@ -840,6 +823,7 @@
android:id="@+id/tvBimiHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:drawableEnd="@drawable/twotone_open_in_new_12"
android:drawablePadding="6dp"
@@ -847,13 +831,14 @@
android:text="@string/title_advanced_privacy_risk"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?attr/colorWarning"
- app:layout_constraintStart_toEndOf="@id/vwFaviconPadding"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swBimi" />
+
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 91a88aa0e0..100c9e4f88 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -495,10 +495,10 @@
Show Brand Indicators for Message Identification (BIMI)
Unverified sender
Verified sender
- Show avatars
+ Show Gravatars
+ Show Libravatars
Show favicons
Scan only the first %1$s of the web page
- Google presumably doesn\'t allow online icons for privacy reasons
Show generated icons
Show identicons
Show round icons