Customizable country codes

Also randomized appId
This commit is contained in:
xynngh 2020-06-23 17:49:24 +04:00
parent 69945a9ce9
commit 3c2479dba5
11 changed files with 274 additions and 24 deletions

View File

@ -56,8 +56,10 @@ Protecting the user's privacy is the first concern during development. No person
The only known possible data leaks are the following:
* Database update procedure leaks user's IP address to the update servers.
The request also includes current database version (base or updated).
The request also includes current database version (base or updated)
and a country code (either auto-detected or set manually).
* Online review requests leak user's IP address coupled with the phone number in question.
The request also includes country codes (either auto-detected or set manually).
Shouldn't be a big deal unless you request it for numbers in your phone book.
If the "use contacts" feature is enabled, a confirmation dialog is shown if online reviews are requested for a number present in your phone book.

View File

@ -39,7 +39,7 @@ dependencies {
implementation 'org.conscrypt:conscrypt-android:2.4.0'
//noinspection GradleDependency: 3.12.* is the latest version compatible with Android <5
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.gitlab.xynngh:LibPhoneNumberInfo:551b0a2f6b'
implementation 'com.gitlab.xynngh:LibPhoneNumberInfo:6d073c991c'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'

View File

@ -45,6 +45,18 @@ public class GenericSettings {
pref.edit().putLong(key, value).apply();
}
public String getString(String key) {
return getString(key, null);
}
public String getString(String key, String defValue) {
return pref.getString(key, defValue);
}
public void setString(String key, String value) {
pref.edit().putString(key, value).apply();
}
public boolean isSet(String key) {
return pref.contains(key);
}

View File

@ -97,7 +97,8 @@ public class ReviewsActivity extends AppCompatActivity {
= new AsyncTask<String, Void, List<CommunityReview>>() {
@Override
protected List<CommunityReview> doInBackground(String... params) {
return DatabaseSingleton.getCommunityReviewsLoader().loadReviews(params[0]);
return DatabaseSingleton.getCommunityReviewsLoader()
.loadReviews(params[0], App.getSettings().getCountryCodeForReviews());
}
@Override

View File

@ -1,9 +1,12 @@
package dummydomain.yetanothercallblocker;
import android.content.Context;
import android.text.TextUtils;
import androidx.preference.PreferenceManager;
import dummydomain.yetanothercallblocker.data.CountryHelper;
public class Settings extends GenericSettings {
public static final String PREF_INCOMING_CALL_NOTIFICATIONS = "incomingCallNotifications";
@ -13,11 +16,15 @@ public class Settings extends GenericSettings {
public static final String PREF_NOTIFICATIONS_UNKNOWN = "showNotificationsForUnknownCallers";
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";
public static final String PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE = "countryCodeForReviewsOverride";
private static final String SYS_PREFERENCES_VERSION = "__preferencesVersion";
private static final int PREFERENCES_VERSION = 1;
private volatile String cachedAutoDetectedCountryCode;
Settings(Context context) {
super(context, PreferenceManager.getDefaultSharedPreferences(context));
}
@ -108,4 +115,46 @@ public class Settings extends GenericSettings {
setLong(PREF_LAST_UPDATE_CHECK_TIME, timestamp);
}
public String getCountryCodeOverride() {
return getString(PREF_COUNTRY_CODE_OVERRIDE);
}
public void setCountryCodeOverride(String code) {
setString(PREF_COUNTRY_CODE_OVERRIDE, code);
}
public String getCountryCodeForReviewsOverride() {
return getString(PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE);
}
public void setCountryCodeForReviewsOverride(String code) {
setString(PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE, code);
}
public String getCountryCode() {
String override = getCountryCodeOverride();
if (!TextUtils.isEmpty(override)) return override.toUpperCase();
return getCachedAutoDetectedCountryCode();
}
public String getCountryCodeForReviews() {
String override = getCountryCodeForReviewsOverride();
if (!TextUtils.isEmpty(override)) return override.toUpperCase();
String code = getCachedAutoDetectedCountryCode();
return !TextUtils.isEmpty(code) ? code : "US";
}
public String getCachedAutoDetectedCountryCode() {
String code = cachedAutoDetectedCountryCode;
if (code == null) {
code = CountryHelper.detectCountry(context);
if (TextUtils.isEmpty(code)) code = "";
cachedAutoDetectedCountryCode = code;
}
return code;
}
}

View File

@ -3,34 +3,43 @@ package dummydomain.yetanothercallblocker;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import java.util.regex.Pattern;
import dummydomain.yetanothercallblocker.work.UpdateScheduler;
import static java.util.Objects.requireNonNull;
public class SettingsActivity extends AppCompatActivity {
public class SettingsActivity extends AppCompatActivity
implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
@ -38,6 +47,21 @@ public class SettingsActivity extends AppCompatActivity {
}
}
@Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
SettingsFragment fragment = new SettingsFragment();
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.settings, fragment, preferenceScreen.getKey());
ft.addToBackStack(preferenceScreen.getKey());
ft.commit();
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
@ -68,6 +92,8 @@ public class SettingsActivity extends AppCompatActivity {
private static final String PREF_USE_CALL_SCREENING_SERVICE = "useCallScreeningService";
private static final String PREF_AUTO_UPDATE_ENABLED = "autoUpdateEnabled";
private static final String PREF_CATEGORY_NOTIFICATIONS = "categoryNotifications";
private static final String PREF_SCREEN_ADVANCED = "screenAdvanced";
private static final String PREF_COUNTRY_CODES_INFO = "countryCodesInfo";
private final UpdateScheduler updateScheduler = UpdateScheduler.get(App.getInstance());
@ -75,6 +101,28 @@ public class SettingsActivity extends AppCompatActivity {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
initRootScreen(rootKey);
initAdvancedScreen(rootKey);
PreferenceScreen preferenceScreen = getPreferenceScreen();
int count = preferenceScreen.getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference preference = preferenceScreen.getPreference(i);
preference.setIconSpaceReserved(false);
if (preference instanceof PreferenceGroup) {
PreferenceGroup group = (PreferenceGroup) preference;
int nestedCount = group.getPreferenceCount();
for (int k = 0; k < nestedCount; k++) {
Preference nested = group.getPreference(k);
nested.setIconSpaceReserved(false);
}
}
}
}
private void initRootScreen(String rootKey) {
if (rootKey != null) return;
SwitchPreferenceCompat incomingCallNotificationPref =
requireNonNull(findPreference(Settings.PREF_INCOMING_CALL_NOTIFICATIONS));
incomingCallNotificationPref.setOnPreferenceChangeListener((preference, newValue) -> {
@ -136,21 +184,46 @@ public class SettingsActivity extends AppCompatActivity {
Preference category = requireNonNull(findPreference(PREF_CATEGORY_NOTIFICATIONS));
category.setVisible(false);
}
}
PreferenceScreen preferenceScreen = getPreferenceScreen();
int count = preferenceScreen.getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference preference = preferenceScreen.getPreference(i);
preference.setIconSpaceReserved(false);
if (preference instanceof PreferenceGroup) {
PreferenceGroup group = (PreferenceGroup) preference;
int nestedCount = group.getPreferenceCount();
for (int k = 0; k < nestedCount; k++) {
Preference nested = group.getPreference(k);
nested.setIconSpaceReserved(false);
}
private void initAdvancedScreen(String rootKey) {
if (!PREF_SCREEN_ADVANCED.equals(rootKey)) return;
String countryCodesExplanationSummary = getString(R.string.country_codes_info_summary)
+ ". " + getString(R.string.country_codes_info_summary_addition,
App.getSettings().getCachedAutoDetectedCountryCode());
Preference countryCodesInfoPreference
= requireNonNull(findPreference(PREF_COUNTRY_CODES_INFO));
countryCodesInfoPreference.setSummary(countryCodesExplanationSummary);
countryCodesInfoPreference.setOnPreferenceClickListener(preference -> {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.settings_category_country_codes)
.setMessage(countryCodesExplanationSummary)
.setNegativeButton(R.string.back, null)
.show();
return true;
});
Preference.OnPreferenceChangeListener countryCodeChangeListener
= (preference, newValue) -> {
String value = (String) newValue;
if (TextUtils.isEmpty(value) || Pattern.matches("^[a-zA-Z]{2}$", value)) {
return true;
}
}
Toast.makeText(getActivity(), R.string.country_code_incorrect_format,
Toast.LENGTH_SHORT).show();
return false;
};
EditTextPreference countryCodePreference
= requireNonNull(findPreference(Settings.PREF_COUNTRY_CODE_OVERRIDE));
countryCodePreference.setOnPreferenceChangeListener(countryCodeChangeListener);
EditTextPreference countryCodeForReviewsPreference
= requireNonNull(findPreference(Settings.PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE));
countryCodeForReviewsPreference.setOnPreferenceChangeListener(countryCodeChangeListener);
}
public void updateCallScreeningPreference() {

View File

@ -3,6 +3,8 @@ package dummydomain.yetanothercallblocker.data;
import android.content.Context;
import android.text.TextUtils;
import java.util.concurrent.TimeUnit;
import dummydomain.yetanothercallblocker.PermissionHelper;
import dummydomain.yetanothercallblocker.sia.Settings;
import dummydomain.yetanothercallblocker.sia.SettingsImpl;
@ -14,6 +16,7 @@ import dummydomain.yetanothercallblocker.sia.model.database.CommunityDatabase;
import dummydomain.yetanothercallblocker.sia.model.database.DbManager;
import dummydomain.yetanothercallblocker.sia.model.database.FeaturedDatabase;
import dummydomain.yetanothercallblocker.sia.network.WebService;
import dummydomain.yetanothercallblocker.sia.utils.Utils;
import static dummydomain.yetanothercallblocker.data.SiaConstants.SIA_PATH_PREFIX;
import static dummydomain.yetanothercallblocker.data.SiaConstants.SIA_PROPERTIES;
@ -22,9 +25,17 @@ import static dummydomain.yetanothercallblocker.data.SiaConstants.SIA_SECONDARY_
public class Config {
private static class WSParameterProvider extends WebService.DefaultWSParameterProvider {
dummydomain.yetanothercallblocker.Settings settings;
SiaMetadata siaMetadata;
CommunityDatabase communityDatabase;
volatile String appId;
volatile long appIdTimestamp;
void setSettings(dummydomain.yetanothercallblocker.Settings settings) {
this.settings = settings;
}
void setSiaMetadata(SiaMetadata siaMetadata) {
this.siaMetadata = siaMetadata;
}
@ -35,7 +46,18 @@ public class Config {
@Override
public String getAppId() {
return "qQq0O9nCRNy_aVdPgU9WOA";
String appId = this.appId;
if (appId != null && System.nanoTime() >
appIdTimestamp + TimeUnit.MINUTES.toNanos(5)) {
appId = null;
}
if (appId == null) {
this.appId = appId = Utils.generateAppId();
appIdTimestamp = System.nanoTime();
}
return appId;
}
@Override
@ -52,6 +74,11 @@ public class Config {
public int getDbVersion() {
return communityDatabase.getEffectiveDbVersion();
}
@Override
public String getCountry() {
return siaMetadata.getCountry(settings.getCountryCode()).code;
}
}
public static void init(Context context, dummydomain.yetanothercallblocker.Settings settings) {
@ -60,6 +87,8 @@ public class Config {
= new SettingsImpl(new AndroidProperties(context, SIA_PROPERTIES));
WSParameterProvider wsParameterProvider = new WSParameterProvider();
wsParameterProvider.setSettings(settings);
WebService webService = new WebService(wsParameterProvider);
DatabaseSingleton.setDbManager(new DbManager(storage, SIA_PATH_PREFIX));
@ -80,8 +109,7 @@ public class Config {
DatabaseSingleton.setFeaturedDatabase(new FeaturedDatabase(
storage, AbstractDatabase.Source.ANY, SIA_PATH_PREFIX));
DatabaseSingleton.setCommunityReviewsLoader(
new CommunityReviewsLoader(webService, "US"));
DatabaseSingleton.setCommunityReviewsLoader(new CommunityReviewsLoader(webService));
DatabaseSingleton.setContactsProvider(number -> {
if (settings.getUseContacts() && PermissionHelper.hasContactsPermission(context)) {

View File

@ -0,0 +1,40 @@
package dummydomain.yetanothercallblocker.data;
import android.content.Context;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.core.os.ConfigurationCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CountryHelper {
private static final Logger LOG = LoggerFactory.getLogger(CountryHelper.class);
public static String detectCountry(Context context) {
try {
TelephonyManager tm = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
String countryCode = tm.getNetworkCountryIso();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
countryCode = tm.getSimCountryIso();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
}
String countryCode = ConfigurationCompat
.getLocales(context.getResources().getConfiguration())
.get(0).getCountry();
if (countryCode.length() == 2) return countryCode;
} catch (Exception e) {
LOG.warn("detectCountry()", e);
}
return null;
}
}

View File

@ -84,4 +84,14 @@
<string name="show_notifications_for_known_callers_summary">Показывать уведомления для известных звонящих (номеров из телефонной книги)</string>
<string name="show_notifications_for_unknown_callers">Уведомления для неизвестных звонящих</string>
<string name="show_notifications_for_unknown_callers_summary">Показывать уведомления для неизвестных звонящих (отсутствующих в телефонной книге и в базе номеров)</string>
<string name="settings_screen_advanced">Продвинутые настройки</string>
<string name="settings_category_country_codes">Коды страны</string>
<string name="country_codes_info">Объяснение</string>
<string name="country_codes_info_summary">Коды страны используются в запросах к сторонним серверам. Эти коды отправляются, для того чтобы имитировать поведение официального приложения. Если коды соответствуют вашей настоящей стране (которая может быть определена по вашему IP-адресу), то запросы будут выглядеть наиболее неприметными. По умолчанию используется автоопределение (по информации мобильной сети или выбранной локали), которое должно устраивать большинство пользователей, но вы можете задать эти коды вручную. Ожидаются двухбуквенные ISO 3166 коды (такие как RU)</string>
<string name="country_codes_info_summary_addition">Автоопределено: %s</string>
<string name="country_code_override">Код страны</string>
<string name="country_code_override_summary">Код страны, используемый во всех запросах. Оставьте пустым для автоопределения</string>
<string name="country_code_for_reviews_override">Код страны для отзывов</string>
<string name="country_code_for_reviews_override_summary">Код страны, используемый для запроса онлайн-отзывов. Предназначен для представления страны звонящего. Оставьте пустым для автоопределения</string>
<string name="country_code_incorrect_format">Некорректный формат кода страны. Значение не обновлено</string>
</resources>

View File

@ -98,6 +98,17 @@
<string name="use_contacts">Use contacts</string>
<string name="use_contacts_summary">Numbers present in the phone book are never blocked and the contact name is displayed next to/instead of a number throughout the app</string>
<string name="settings_screen_advanced">Advanced settings</string>
<string name="settings_category_country_codes">Country codes</string>
<string name="country_codes_info">Explanation</string>
<string name="country_codes_info_summary">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</string>
<string name="country_codes_info_summary_addition">Auto-detected: %s</string>
<string name="country_code_override">Country code</string>
<string name="country_code_override_summary">Country code used in all requests. Leave empty for auto-detection</string>
<string name="country_code_for_reviews_override">Country code for reviews</string>
<string name="country_code_for_reviews_override_summary">Country code used in requests for online reviews. Meant to represent the country of the caller. Leave empty for auto-detection</string>
<string name="country_code_incorrect_format">Incorrect country code format. Value is not updated</string>
<string name="open_debug_activity">Open debug screen</string>
<string name="debug_activity_label">Debug</string>
<string name="debug_query_db">Query DB</string>

View File

@ -42,4 +42,28 @@
app:title="@string/show_notifications_for_unknown_callers" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_screen_advanced">
<PreferenceScreen
app:key="screenAdvanced"
app:title="@string/settings_screen_advanced">
<PreferenceCategory
app:key="categoryCountryCodes"
app:title="@string/settings_category_country_codes">
<Preference
app:key="countryCodesInfo"
app:persistent="false"
app:summary="@string/country_codes_info_summary"
app:title="@string/country_codes_info" />
<EditTextPreference
app:key="countryCodeOverride"
app:summary="@string/country_code_override_summary"
app:title="@string/country_code_override" />
<EditTextPreference
app:key="countryCodeForReviewsOverride"
app:summary="@string/country_code_for_reviews_override_summary"
app:title="@string/country_code_for_reviews_override" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>