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