diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c35ae..585cabe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed +- Changed blocking behavior in Direct Boot mode: blacklisted numbers are not blocked by default. + See #22 for details. - \[Internal\] Settings refactoring. - Updated Croatian translation thanks to Milo Ivir (@milotype). - Updated French translation thanks to J. Lavoie ([@Edanas](https://hosted.weblate.org/user/Edanas/)). diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/AdvancedSettingsFragment.java b/app/src/main/java/dummydomain/yetanothercallblocker/AdvancedSettingsFragment.java index 09a0a06..4c962b2 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/AdvancedSettingsFragment.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/AdvancedSettingsFragment.java @@ -5,6 +5,7 @@ import android.text.TextUtils; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; +import androidx.preference.MultiSelectListPreference; import androidx.preference.Preference; import org.slf4j.Logger; @@ -16,6 +17,7 @@ import java.util.regex.Pattern; import dummydomain.yetanothercallblocker.utils.DebuggingUtils; import dummydomain.yetanothercallblocker.utils.FileUtils; +import dummydomain.yetanothercallblocker.utils.SystemUtils; public class AdvancedSettingsFragment extends BaseSettingsFragment { @@ -37,6 +39,17 @@ public class AdvancedSettingsFragment extends BaseSettingsFragment { @Override protected void initScreen() { + Preference blockInLimitedModePref = + requirePreference(Settings.PREF_BLOCK_IN_LIMITED_MODE); + if (SystemUtils.isFileBasedEncryptionEnabled()) { + blockInLimitedModePref.setSummaryProvider( + (Preference.SummaryProvider) preference -> + getString(R.string.block_in_limited_mode_summary) + ".\n" + + UiUtils.getSummary(requireContext(), preference)); + } else { + blockInLimitedModePref.setVisible(false); + } + String countryCodesExplanationSummary = getString(R.string.country_codes_info_summary) + ". " + getString(R.string.country_codes_info_summary_addition, App.getSettings().getCachedAutoDetectedCountryCode()); diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/GenericSettings.java b/app/src/main/java/dummydomain/yetanothercallblocker/GenericSettings.java index 43bf77c..cd42579 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/GenericSettings.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/GenericSettings.java @@ -4,6 +4,10 @@ import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; +import androidx.core.util.Supplier; + +import java.util.Set; + public class GenericSettings { protected final Context context; @@ -64,6 +68,19 @@ public class GenericSettings { pref.edit().putString(key, value).apply(); } + public Set getStringSet(String key, Set defValue) { + return pref.getStringSet(key, defValue); + } + + public Set getStringSet(String key, Supplier> defValueSupplier) { + Set val = pref.getStringSet(key, null); + return val != null ? val : defValueSupplier.get(); + } + + public void setStringSet(String key, Set value) { + pref.edit().putStringSet(key, value).apply(); + } + public boolean isSet(String key) { return pref.contains(key); } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/Settings.java b/app/src/main/java/dummydomain/yetanothercallblocker/Settings.java index 9392592..1bbef93 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/Settings.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/Settings.java @@ -9,7 +9,10 @@ import androidx.preference.PreferenceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; import dummydomain.yetanothercallblocker.data.CountryHelper; import dummydomain.yetanothercallblocker.sia.model.database.DbManager; @@ -28,6 +31,7 @@ public class Settings extends GenericSettings { public static final String PREF_NOTIFICATIONS_KNOWN = "showNotificationsForKnownCallers"; public static final String PREF_NOTIFICATIONS_UNKNOWN = "showNotificationsForUnknownCallers"; public static final String PREF_NOTIFICATIONS_BLOCKED = "showNotificationsForBlockedCalls"; + public static final String PREF_BLOCK_IN_LIMITED_MODE = "blockInLimitedMode"; public static final String PREF_LAST_UPDATE_TIME = "lastUpdateTime"; public static final String PREF_LAST_UPDATE_CHECK_TIME = "lastUpdateCheckTime"; public static final String PREF_COUNTRY_CODE_OVERRIDE = "countryCodeOverride"; @@ -40,6 +44,9 @@ public class Settings extends GenericSettings { public static final String PREF_CALL_LOG_GROUPING_CONSECUTIVE = "consecutive"; public static final String PREF_CALL_LOG_GROUPING_DAY = "day"; + public static final String PREF_BLOCK_IN_LIMITED_MODE_RATING = "rating"; + public static final String PREF_BLOCK_IN_LIMITED_MODE_BLACKLIST = "blacklist"; + static final String SYS_PREFERENCES_VERSION = "__preferencesVersion"; private static final Logger LOG = LoggerFactory.getLogger(Settings.class); @@ -201,6 +208,24 @@ public class Settings extends GenericSettings { setBoolean(PREF_NOTIFICATIONS_BLOCKED, show); } + public boolean isBlockingByRatingInLimitedModeAllowed() { + return getBlockInLimitedMode().contains(PREF_BLOCK_IN_LIMITED_MODE_RATING); + } + + public boolean isBlockingBlacklistedInLimitedModeAllowed() { + return getBlockInLimitedMode().contains(PREF_BLOCK_IN_LIMITED_MODE_BLACKLIST); + } + + public Set getBlockInLimitedMode() { + return getStringSet(PREF_BLOCK_IN_LIMITED_MODE, () -> + new HashSet<>(Arrays.asList(context.getResources() + .getStringArray(R.array.block_in_limited_mode_default_values)))); + } + + public void setBlockInLimitedMode(Set value) { + setStringSet(PREF_BLOCK_IN_LIMITED_MODE, value); + } + public long getLastUpdateTime() { return getLong(PREF_LAST_UPDATE_TIME, 0); } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/UiUtils.java b/app/src/main/java/dummydomain/yetanothercallblocker/UiUtils.java index d2e9e75..7ed30ab 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/UiUtils.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/UiUtils.java @@ -1,11 +1,18 @@ package dummydomain.yetanothercallblocker; import android.content.Context; +import android.text.TextUtils; import androidx.annotation.ColorInt; import androidx.annotation.ColorRes; import androidx.annotation.NonNull; import androidx.core.content.res.ResourcesCompat; +import androidx.preference.MultiSelectListPreference; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; public class UiUtils { @@ -14,4 +21,34 @@ public class UiUtils { return ResourcesCompat.getColor(context.getResources(), colorResId, context.getTheme()); } + public static String getSummary(@NonNull Context context, + @NonNull MultiSelectListPreference preference) { + List selectedEntries = getSelectedEntries(preference); + + String valuesString = selectedEntries.isEmpty() + ? context.getString(R.string.selected_value_nothing) + : TextUtils.join(", ", selectedEntries); + + return context.getResources().getQuantityString(R.plurals.selected_values, + selectedEntries.size(), valuesString); + } + + public static List getSelectedEntries(MultiSelectListPreference preference) { + CharSequence[] entries = preference.getEntries(); + CharSequence[] entryValues = preference.getEntryValues(); + Set values = preference.getValues(); + + if (values.isEmpty()) return Collections.emptyList(); + + List result = new ArrayList<>(values.size()); + + for (int i = 0; i < entries.length; i++) { + if (values.contains(entryValues[i].toString())) { + result.add(entries[i].toString()); + } + } + + return result; + } + } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/data/Config.java b/app/src/main/java/dummydomain/yetanothercallblocker/data/Config.java index ed6a2e1..ec2f360 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/data/Config.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/data/Config.java @@ -22,6 +22,7 @@ import dummydomain.yetanothercallblocker.sia.network.OkHttpClientFactory; import dummydomain.yetanothercallblocker.sia.network.WebService; import dummydomain.yetanothercallblocker.sia.utils.Utils; import dummydomain.yetanothercallblocker.utils.DeferredInit; +import dummydomain.yetanothercallblocker.utils.SystemUtils; import okhttp3.OkHttpClient; import static dummydomain.yetanothercallblocker.data.SiaConstants.SIA_PATH_PREFIX; @@ -134,8 +135,17 @@ public class Config { settings::setBlacklistIsNotEmpty, blacklistDao); YacbHolder.setBlacklistService(blacklistService); - ContactsProvider contactsProvider = number -> - settings.getUseContacts() ? ContactsHelper.getContact(context, number) : null; + ContactsProvider contactsProvider = new ContactsProvider() { + @Override + public ContactItem get(String number) { + return settings.getUseContacts() ? ContactsHelper.getContact(context, number) : null; + } + + @Override + public boolean isInLimitedMode() { + return !SystemUtils.isUserUnlocked(context); + } + }; NumberInfoService numberInfoService = new NumberInfoService( settings, NumberUtils::isHiddenNumber, NumberUtils::normalizeNumber, diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/data/ContactsProvider.java b/app/src/main/java/dummydomain/yetanothercallblocker/data/ContactsProvider.java index 82626c8..3f0c2ea 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/data/ContactsProvider.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/data/ContactsProvider.java @@ -1,5 +1,9 @@ package dummydomain.yetanothercallblocker.data; public interface ContactsProvider { + ContactItem get(String number); + + boolean isInLimitedMode(); + } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/data/NumberInfoService.java b/app/src/main/java/dummydomain/yetanothercallblocker/data/NumberInfoService.java index 6881400..3cfebb2 100644 --- a/app/src/main/java/dummydomain/yetanothercallblocker/data/NumberInfoService.java +++ b/app/src/main/java/dummydomain/yetanothercallblocker/data/NumberInfoService.java @@ -136,17 +136,38 @@ public class NumberInfoService { } if (numberInfo.rating == NumberInfo.Rating.NEGATIVE - && settings.getBlockNegativeSiaNumbers()) { + && settings.getBlockNegativeSiaNumbers() + && canBlock(NumberInfo.BlockingReason.SIA_RATING)) { return NumberInfo.BlockingReason.SIA_RATING; } - if (numberInfo.blacklistItem != null && settings.getBlockBlacklisted()) { + if (numberInfo.blacklistItem != null && settings.getBlockBlacklisted() + && canBlock(NumberInfo.BlockingReason.BLACKLISTED)) { return NumberInfo.BlockingReason.BLACKLISTED; } return null; } + protected boolean canBlock(NumberInfo.BlockingReason reason) { + if (contactsProvider == null || !contactsProvider.isInLimitedMode()) return true; + + if (reason == NumberInfo.BlockingReason.SIA_RATING + && settings.isBlockingByRatingInLimitedModeAllowed()) { + LOG.trace("canBlock() allowed: " + reason); + return true; + } + + if (reason == NumberInfo.BlockingReason.BLACKLISTED + && settings.isBlockingBlacklistedInLimitedModeAllowed()) { + LOG.trace("canBlock() allowed: " + reason); + return true; + } + + LOG.trace("canBlock() not allowed: " + reason); + return false; + } + public boolean shouldBlock(NumberInfo numberInfo) { return numberInfo.blockingReason != null; } diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/utils/SystemUtils.java b/app/src/main/java/dummydomain/yetanothercallblocker/utils/SystemUtils.java new file mode 100644 index 0000000..4c7b532 --- /dev/null +++ b/app/src/main/java/dummydomain/yetanothercallblocker/utils/SystemUtils.java @@ -0,0 +1,62 @@ +package dummydomain.yetanothercallblocker.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; + +import androidx.core.os.UserManagerCompat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +public class SystemUtils { + + private static final Logger LOG = LoggerFactory.getLogger(SystemUtils.class); + + private static Boolean fileBasedEncryptionEnabled; + private static boolean userUnlocked; + + public static boolean isFileBasedEncryptionEnabled() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false; + + Boolean enabled = fileBasedEncryptionEnabled; + if (enabled == null) { + enabled = fileBasedEncryptionEnabled = isFileBasedEncryptionEnabledInternal(); + } + + return enabled; + } + + private static Boolean isFileBasedEncryptionEnabledInternal() { + try { + @SuppressLint("PrivateApi") + Class cls = Class.forName("android.os.SystemProperties"); + Method get = cls.getMethod("get", String.class); + + String type = (String) get.invoke(null, "ro.crypto.type"); + if (!TextUtils.equals(type, "file")) return false; + + String state = (String) get.invoke(null, "ro.crypto.state"); + return TextUtils.equals(state, "encrypted"); + } catch (Exception e) { + LOG.warn("isFileBasedEncryptionEnabledInternal()", e); + } + + return true; // *assume* it is enabled, if the check fails + } + + public static boolean isUserUnlocked(Context context) { + if (userUnlocked) return true; + + if (UserManagerCompat.isUserUnlocked(context)) { + userUnlocked = true; + return true; + } + + return false; + } + +} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 09645f0..e3936cf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -110,6 +110,10 @@ Всё равно выключить Настройки уведомлений Продвинутые настройки + Блокировать в режиме Direct Boot + Типы блокировки, разрешённые в режиме Direct Boot (контакты не могут надёжно выполнять роль белого списка в этом режиме) + По отзывам + Из чёрного списока Коды страны Объяснение Коды страны используются в запросах к сторонним серверам. Эти коды отправляются, для того чтобы имитировать поведение официального приложения. Если коды соответствуют вашей настоящей стране (которая может быть определена по вашему IP-адресу), то запросы будут выглядеть наиболее неприметными. По умолчанию используется автоопределение (по информации мобильной сети или выбранной локали), которое должно устраивать большинство пользователей, но вы можете задать эти коды вручную. Ожидаются двухбуквенные ISO 3166 коды (такие как RU) @@ -134,6 +138,12 @@ %1$d выбрано %1$d выбрано + + Выбраны: %s + Выбраны: %s + Выбраны: %s + + <ничего> Чёрный список Чёрный список diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 0b299aa..9afe930 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -10,6 +10,7 @@ 2 -1 + @string/call_log_grouping_none @string/call_log_grouping_consecutive @@ -20,4 +21,16 @@ consecutive day + + + @string/block_in_limited_mode_rating + @string/block_in_limited_mode_blacklist + + + rating + blacklist + + + rating + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 64bda37..d128675 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,6 +128,10 @@ Non-consecutive in a day Advanced settings + Block in Direct Boot mode + Allowed kinds of blocking in Direct Boot mode (Contacts can\'t be reliably whitelisted in this mode) + By rating + Blacklisted Country codes Explanation Country codes are used in requests to 3rd party servers. These codes are set to mimic the behavior of the official 3rd party app. If the codes match your real country (may be detected by your IP address) then your requests will look the most inconspicuous. Auto-detection is used by default (based on mobile network information or system locale), which should be fine for most users, but you can set the codes manually. ISO 3166 2-letter codes (like US) are expected @@ -151,6 +155,11 @@ %1$d selected %1$d selected + + Selected: %s + Selected: %s + + <nothing> Blacklist Blacklist diff --git a/app/src/main/res/xml/advanced_preferences.xml b/app/src/main/res/xml/advanced_preferences.xml index a847a2c..5ab061a 100644 --- a/app/src/main/res/xml/advanced_preferences.xml +++ b/app/src/main/res/xml/advanced_preferences.xml @@ -13,6 +13,14 @@ android:targetPackage="@string/app_id" /> + +