diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index f8fca744c4..2dbac0897d 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -3373,7 +3373,7 @@ class Core { boolean blocklist = false; for (Address sender : senders) { String email = ((InternetAddress) sender).getAddress(); - if (DnsBlockList.isJunk(email)) { + if (DnsBlockList.isJunk(context, email)) { blocklist = true; break; } diff --git a/app/src/main/java/eu/faircode/email/DnsBlockList.java b/app/src/main/java/eu/faircode/email/DnsBlockList.java index 1e84ddd619..3d41a04859 100644 --- a/app/src/main/java/eu/faircode/email/DnsBlockList.java +++ b/app/src/main/java/eu/faircode/email/DnsBlockList.java @@ -19,8 +19,12 @@ package eu.faircode.email; Copyright 2018-2021 by Marcel Bokhorst (M66B) */ +import android.content.Context; +import android.content.SharedPreferences; import android.text.TextUtils; +import androidx.preference.PreferenceManager; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -34,7 +38,7 @@ import java.util.List; import java.util.Map; public class DnsBlockList { - private static final List BLOCKLISTS = Collections.unmodifiableList(Arrays.asList( + static final List BLOCKLISTS = Collections.unmodifiableList(Arrays.asList( // https://www.spamhaus.org/zen/ new BlockList(true, "Spamhaus/zen", "zen.spamhaus.org", true, new String[]{ // https://www.spamhaus.org/faq/section/DNSBL%20Usage#200 @@ -73,15 +77,29 @@ public class DnsBlockList { private static final long CACHE_EXPIRY_AFTER = 3600 * 1000L; // milliseconds private static final Map cache = new Hashtable<>(); - static List getNames() { + static void setEnabled(Context context, BlockList blocklist, boolean enabled) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit().putBoolean("blocklist." + blocklist.name, enabled).apply(); + + synchronized (cache) { + cache.clear(); + } + } + + static boolean isEnabled(Context context, BlockList blocklist) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getBoolean("blocklist." + blocklist.name, blocklist.enabled); + } + + static List getNames(Context context) { List names = new ArrayList<>(); for (BlockList blocklist : BLOCKLISTS) - if (blocklist.enabled) + if (isEnabled(context, blocklist)) names.add(blocklist.name); return names; } - static boolean isJunk(String email) { + static boolean isJunk(Context context, String email) { if (TextUtils.isEmpty(email)) return false; @@ -89,10 +107,10 @@ public class DnsBlockList { if (at < 0) return false; - return isJunk(email.substring(at + 1), BLOCKLISTS); + return isJunk(context, email.substring(at + 1), BLOCKLISTS); } - private static boolean isJunk(String domain, List blocklists) { + private static boolean isJunk(Context context, String domain, List blocklists) { synchronized (cache) { CacheEntry entry = cache.get(domain); if (entry != null && !entry.isExpired()) @@ -101,7 +119,7 @@ public class DnsBlockList { boolean blocked = false; for (BlockList blocklist : blocklists) - if (blocklist.enabled && isJunk(domain, blocklist)) { + if (isEnabled(context, blocklist) && isJunk(domain, blocklist)) { blocked = true; break; } @@ -212,13 +230,17 @@ public class DnsBlockList { } static class BlockList { + int id; boolean enabled; String name; String address; boolean numeric; InetAddress[] responses; + private static int nextid = 1; + BlockList(boolean enabled, String name, String address, boolean numeric, String[] responses) { + this.id = nextid++; this.enabled = enabled; this.name = name; this.address = address; diff --git a/app/src/main/java/eu/faircode/email/FragmentDialogJunk.java b/app/src/main/java/eu/faircode/email/FragmentDialogJunk.java index 5b6233fb11..4cb8813ba8 100644 --- a/app/src/main/java/eu/faircode/email/FragmentDialogJunk.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogJunk.java @@ -245,7 +245,7 @@ public class FragmentDialogJunk extends FragmentDialogBase { cbBlockDomain.setEnabled(false); ibMore.setImageLevel(1); cbBlocklist.setChecked(check_blocklist && use_blocklist); - tvBlocklist.setText(TextUtils.join(", ", DnsBlockList.getNames())); + tvBlocklist.setText(TextUtils.join(", ", DnsBlockList.getNames(context))); grpInJunk.setVisibility(inJunk ? View.GONE : View.VISIBLE); grpMore.setVisibility(inJunk ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsSynchronize.java b/app/src/main/java/eu/faircode/email/FragmentOptionsSynchronize.java index f651a8502e..970712682d 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsSynchronize.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsSynchronize.java @@ -24,7 +24,6 @@ import android.app.TimePickerDialog; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.text.TextUtils; import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.Menu; @@ -86,7 +85,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr private SwitchCompat swCheckReply; private SwitchCompat swCheckMx; private SwitchCompat swCheckBlocklist; - private TextView tvCheckBlocklistHint; + private RecyclerView rvBlocklist; private SwitchCompat swUseBlocklist; private SwitchCompat swTuneKeepAlive; private Group grpExempted; @@ -144,7 +143,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr swCheckReply = view.findViewById(R.id.swCheckReply); swCheckMx = view.findViewById(R.id.swCheckMx); swCheckBlocklist = view.findViewById(R.id.swCheckBlocklist); - tvCheckBlocklistHint = view.findViewById(R.id.tvCheckBlocklistHint); + rvBlocklist = view.findViewById(R.id.rvBlocklist); swUseBlocklist = view.findViewById(R.id.swUseBlocklist); swTuneKeepAlive = view.findViewById(R.id.swTuneKeepAlive); grpExempted = view.findViewById(R.id.grpExempted); @@ -346,9 +345,15 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { prefs.edit().putBoolean("check_blocklist", checked).apply(); swUseBlocklist.setEnabled(checked); + rvBlocklist.setVisibility(checked ? View.VISIBLE : View.GONE); } }); + rvBlocklist.setHasFixedSize(false); + rvBlocklist.setLayoutManager(new LinearLayoutManager(getContext())); + AdapterBlocklist badapter = new AdapterBlocklist(getContext(), DnsBlockList.BLOCKLISTS); + rvBlocklist.setAdapter(badapter); + swUseBlocklist.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -381,8 +386,6 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr } }); - tvCheckBlocklistHint.setText(TextUtils.join(", ", DnsBlockList.getNames())); - PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); return view; @@ -454,6 +457,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr swCheckReply.setChecked(prefs.getBoolean("check_reply_domain", true)); swCheckMx.setChecked(prefs.getBoolean("check_mx", false)); swCheckBlocklist.setChecked(prefs.getBoolean("check_blocklist", false)); + rvBlocklist.setVisibility(swCheckBlocklist.isChecked() ? View.VISIBLE : View.GONE); swUseBlocklist.setChecked(prefs.getBoolean("use_blocklist", false)); swUseBlocklist.setEnabled(swCheckBlocklist.isChecked()); swTuneKeepAlive.setChecked(prefs.getBoolean("tune_keep_alive", true)); @@ -502,7 +506,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr } } - public class AdapterAccountExempted extends RecyclerView.Adapter { + public static class AdapterAccountExempted extends RecyclerView.Adapter { private Context context; private LifecycleOwner owner; private LayoutInflater inflater; @@ -641,4 +645,75 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr holder.wire(); } } + + public static class AdapterBlocklist extends RecyclerView.Adapter { + private Context context; + private LayoutInflater inflater; + + private List items; + + public class ViewHolder extends RecyclerView.ViewHolder implements CompoundButton.OnCheckedChangeListener { + private CheckBox cbEnabled; + + ViewHolder(View itemView) { + super(itemView); + cbEnabled = itemView.findViewById(R.id.cbEnabled); + } + + private void wire() { + cbEnabled.setOnCheckedChangeListener(this); + } + + private void unwire() { + cbEnabled.setOnCheckedChangeListener(null); + } + + private void bindTo(DnsBlockList.BlockList blocklist) { + cbEnabled.setText(blocklist.name); + cbEnabled.setChecked(DnsBlockList.isEnabled(context, blocklist)); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + int pos = getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) + return; + + DnsBlockList.BlockList blocklist = items.get(pos); + DnsBlockList.setEnabled(context, blocklist, isChecked); + } + } + + AdapterBlocklist(Context context, List items) { + this.context = context; + this.inflater = LayoutInflater.from(context); + + setHasStableIds(true); + this.items = items; + } + + @Override + public long getItemId(int position) { + return items.get(position).id; + } + + @Override + public int getItemCount() { + return items.size(); + } + + @Override + @NonNull + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(inflater.inflate(R.layout.item_blocklist_enabled, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.unwire(); + DnsBlockList.BlockList blocklist = items.get(position); + holder.bindTo(blocklist); + holder.wire(); + } + } } diff --git a/app/src/main/res/layout/fragment_options_synchronize.xml b/app/src/main/res/layout/fragment_options_synchronize.xml index 0c035b1627..5d8af04786 100644 --- a/app/src/main/res/layout/fragment_options_synchronize.xml +++ b/app/src/main/res/layout/fragment_options_synchronize.xml @@ -653,18 +653,6 @@ app:layout_constraintTop_toBottomOf="@id/tvCheckMxWarning" app:switchPadding="12dp" /> - - + app:layout_constraintTop_toBottomOf="@id/swCheckBlocklist" /> + + + + + + + + + \ No newline at end of file