mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-13 04:25:47 +02:00
Enabled BIMI, Gravatars, Libravatars and favicons for Play store
This commit is contained in:
@@ -6880,9 +6880,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
||||
|
||||
boolean contacts = Helper.hasPermission(context, Manifest.permission.READ_CONTACTS);
|
||||
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);
|
||||
|
||||
this.date = prefs.getBoolean("date", true);
|
||||
@@ -6893,7 +6894,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
||||
this.threading_unread = threading && prefs.getBoolean("threading_unread", false);
|
||||
this.indentation = prefs.getBoolean("indentation", false);
|
||||
|
||||
this.avatars = (contacts && avatars) || (bimi || efavicons || favicons || generated);
|
||||
this.avatars = (contacts && avatars) || (bimi || gravatars || libravatars || favicons || generated);
|
||||
this.color_stripe = prefs.getBoolean("color_stripe", true);
|
||||
this.check_authentication = prefs.getBoolean("check_authentication", true);
|
||||
this.check_tls = prefs.getBoolean("check_tls", true);
|
||||
|
||||
@@ -119,6 +119,14 @@ public class ContactInfo {
|
||||
private static final long CACHE_FAVICON_DURATION = 2 * 7 * 24 * 60 * 60 * 1000L; // milliseconds
|
||||
private static final float MIN_FAVICON_LUMINANCE = 0.2f;
|
||||
|
||||
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/";
|
||||
|
||||
// https://css-tricks.com/prefetching-preloading-prebrowsing/
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch
|
||||
private static final List<String> 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<ContactInfo.Favicon> getGravatar(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@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<ContactInfo.Favicon> getLibravatar(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@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);
|
||||
|
||||
@@ -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<StyleHelper.FontDescriptor> 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));
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user