diff --git a/ChangeLog.md b/ChangeLog.md
index 6ac034171..8593b551b 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1184,3 +1184,36 @@
**InviZible Pro beta 2.3.2**
* Updated Tor.
* Fixes and optimizations.
+
+**InviZible Pro beta 2.3.3**
+* Updated Purple I2P to version 2.54.0.
+* Updated Tor Snowflake bridge.
+* Added option for downloading and updating DNSCrypt blacklists.
+* Tor apps isolation is enabled by default.
+* Fixed requesting Tor bridges via the app.
+* Updated German and Spanish translations.
+
+**InviZible Pro beta 2.3.4**
+* Implemented the ability to use the firewall when other modules are stopped.
+* Added a stop button to the notification.
+* Fixes and optimizations.
+
+**InviZible Pro beta 2.3.5**
+* Updated Tor.
+* Updated Tor Snowflake bridge.
+* Updated Tor Lyrebird bridge to version 0.4.0.
+* Implemented the ability to insert a list of DNSCrypt rules.
+* Fixes and optimizations.
+
+**InviZible Pro beta 2.3.6**
+* Added an option for quick access to Always-on VPN settings.
+* Improvements and fixes for standalone firewall operation.
+* Improvements for the firewall in root mode for Android versions above 5.
+* Removed the Fastly rendezvous of the Tor snowflake bridge.
+* Minor fixes and optimisations.
+
+**InviZible Pro beta 2.3.7**
+* Updated Tor to version 4.8.13.
+* Fixed detection of whether a cellular network interface can provide an internet connection.
+* Fixed I2PD subscriptions option.
+* Updated German, Spanish, Greek, Chinese, Portuguese (Brazil), Persian and Bulgarian translations.
diff --git a/build.gradle b/build.gradle
index b4e694686..0ac11c14d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,9 +3,9 @@
buildscript {
ext {
kotlin_version = '1.9.23'
- dagger_version = '2.51.1'
+ dagger_version = '2.52'
multidex_version = "2.0.1"
- work_version = "2.9.0"
+ work_version = "2.9.1"
}
@@ -14,7 +14,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.5.1'
+ classpath 'com.android.tools.build:gradle:8.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
diff --git a/fastlane/metadata/android/en-US/changelogs/100233.txt b/fastlane/metadata/android/en-US/changelogs/100233.txt
new file mode 100644
index 000000000..a0003d010
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/100233.txt
@@ -0,0 +1,7 @@
+**InviZible Pro beta 2.3.3**
+* Updated Purple I2P to version 2.54.0.
+* Updated Tor Snowflake bridge.
+* Added option for downloading and updating DNSCrypt blacklists.
+* Tor apps isolation is enabled by default.
+* Fixed requesting Tor bridges via the app.
+* Updated German and Spanish translations.
diff --git a/fastlane/metadata/android/en-US/changelogs/100234.txt b/fastlane/metadata/android/en-US/changelogs/100234.txt
new file mode 100644
index 000000000..f2ba0dde5
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/100234.txt
@@ -0,0 +1,4 @@
+**InviZible Pro beta 2.3.4**
+* Implemented the ability to use the firewall when other modules are stopped.
+* Added a stop button to the notification.
+* Fixes and optimizations.
diff --git a/fastlane/metadata/android/en-US/changelogs/100235.txt b/fastlane/metadata/android/en-US/changelogs/100235.txt
new file mode 100644
index 000000000..6deef4e5b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/100235.txt
@@ -0,0 +1,6 @@
+**InviZible Pro beta 2.3.5**
+* Updated Tor.
+* Updated Tor Snowflake bridge.
+* Updated Tor Lyrebird bridge to version 0.4.0.
+* Implemented the ability to insert a list of DNSCrypt rules.
+* Fixes and optimizations.
diff --git a/fastlane/metadata/android/en-US/changelogs/100236.txt b/fastlane/metadata/android/en-US/changelogs/100236.txt
new file mode 100644
index 000000000..2c28fbed5
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/100236.txt
@@ -0,0 +1,6 @@
+**InviZible Pro beta 2.3.6**
+* Added an option for quick access to Always-on VPN settings.
+* Improvements and fixes for standalone firewall operation.
+* Improvements for the firewall in root mode for Android versions above 5.
+* Removed the Fastly rendezvous of the Tor snowflake bridge.
+* Minor fixes and optimisations.
diff --git a/fastlane/metadata/android/en-US/changelogs/100237.txt b/fastlane/metadata/android/en-US/changelogs/100237.txt
new file mode 100644
index 000000000..8d302425b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/100237.txt
@@ -0,0 +1,5 @@
+**InviZible Pro beta 2.3.7**
+* Updated Tor to version 4.8.13.
+* Fixed detection of whether a cellular network interface can provide an internet connection.
+* Fixed I2PD subscriptions option.
+* Updated German, Spanish, Greek, Chinese, Portuguese (Brazil), Persian and Bulgarian translations.
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 85310727a..c87079a3a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,4 +4,4 @@ distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
-distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0
+distributionSha256Sum=distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0
diff --git a/tordnscrypt/build.gradle b/tordnscrypt/build.gradle
index 8e0eee26a..7f49cb527 100644
--- a/tordnscrypt/build.gradle
+++ b/tordnscrypt/build.gradle
@@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
+ id "kotlin-parcelize"
}
android {
@@ -15,7 +16,7 @@ android {
fdroid{
applicationId "pan.alexander.tordnscrypt.stable"
- versionName "6.8.0"
+ versionName "6.9.0"
dimension = 'version'
resValue 'string', 'package_name', applicationId
}
@@ -55,7 +56,7 @@ android {
defaultConfig {
minSdkVersion 19
targetSdkVersion 34
- versionCode 232
+ versionCode 237
resConfigs "en", "ru", "pl", "de", "fa", "fi", "in", "fr", "ja", "zh", "es", "pt", "pt-rBR", "el", "tr", "it", "uk", "bg", "ar"
@@ -125,7 +126,7 @@ android {
}
}
- ndkVersion '23.0.7421159'
+ ndkVersion '23.1.7779620'
buildFeatures {
viewBinding true
@@ -156,16 +157,16 @@ dependencies {
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
- implementation 'androidx.lifecycle:lifecycle-process:2.8.4'
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.6'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation 'eu.chainfire:libsuperuser:1.1.1'
implementation 'com.jrummyapps:android-shell:1.0.1'
implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation project(':filepicker')
diff --git a/tordnscrypt/libs/arm64-v8a/libi2pd.so b/tordnscrypt/libs/arm64-v8a/libi2pd.so
index 62a6755a9..1c916b638 100644
Binary files a/tordnscrypt/libs/arm64-v8a/libi2pd.so and b/tordnscrypt/libs/arm64-v8a/libi2pd.so differ
diff --git a/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so b/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so
index 4719d8643..fa8fb54d5 100755
Binary files a/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so and b/tordnscrypt/libs/arm64-v8a/libobfs4proxy.so differ
diff --git a/tordnscrypt/libs/arm64-v8a/libsnowflake.so b/tordnscrypt/libs/arm64-v8a/libsnowflake.so
index 40d0a2b37..e53399001 100755
Binary files a/tordnscrypt/libs/arm64-v8a/libsnowflake.so and b/tordnscrypt/libs/arm64-v8a/libsnowflake.so differ
diff --git a/tordnscrypt/libs/arm64-v8a/libtor.so b/tordnscrypt/libs/arm64-v8a/libtor.so
index 9a14c87f2..893ec4c37 100644
Binary files a/tordnscrypt/libs/arm64-v8a/libtor.so and b/tordnscrypt/libs/arm64-v8a/libtor.so differ
diff --git a/tordnscrypt/libs/armeabi-v7a/libi2pd.so b/tordnscrypt/libs/armeabi-v7a/libi2pd.so
index 535686e6d..73ba155ba 100644
Binary files a/tordnscrypt/libs/armeabi-v7a/libi2pd.so and b/tordnscrypt/libs/armeabi-v7a/libi2pd.so differ
diff --git a/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so b/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so
index b23067a11..e9cca1e04 100755
Binary files a/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so and b/tordnscrypt/libs/armeabi-v7a/libobfs4proxy.so differ
diff --git a/tordnscrypt/libs/armeabi-v7a/libsnowflake.so b/tordnscrypt/libs/armeabi-v7a/libsnowflake.so
index ca94dabcb..65954c469 100755
Binary files a/tordnscrypt/libs/armeabi-v7a/libsnowflake.so and b/tordnscrypt/libs/armeabi-v7a/libsnowflake.so differ
diff --git a/tordnscrypt/libs/armeabi-v7a/libtor.so b/tordnscrypt/libs/armeabi-v7a/libtor.so
index d68b0fd95..f820fbc7e 100644
Binary files a/tordnscrypt/libs/armeabi-v7a/libtor.so and b/tordnscrypt/libs/armeabi-v7a/libtor.so differ
diff --git a/tordnscrypt/libs/build b/tordnscrypt/libs/build
index 0aaa86368..c4476472e 100755
--- a/tordnscrypt/libs/build
+++ b/tordnscrypt/libs/build
@@ -6,7 +6,7 @@ cd "$( dirname "${BASH_SOURCE[0]}" )"
## Config ##
############
-ndk_version=21.3.6528147
+ndk_version=23.1.7779620
if [[ -d "/opt/android-sdk/ndk/${ndk_version}" ]]
then
NDK="/opt/android-sdk/ndk/${ndk_version}"
@@ -78,7 +78,7 @@ popd
################
pushd libzmq/builds/android/
-export NDK_VERSION="android-ndk-r21c"
+export NDK_VERSION="android-ndk-r23b"
export ANDROID_NDK_ROOT=$NDK
if [[ $ABI == arm64-v8a ]]
then
@@ -145,7 +145,7 @@ then
NDK_PLATFORM_LEVEL=21 NDK_BIT=64 make showsetup
else
#compile armeabi-v7a things...
- export APP_ABI=armeabi
+ export APP_ABI=armeabi-v7a
make clean
make
make showsetup
diff --git a/tordnscrypt/libs/prebuild b/tordnscrypt/libs/prebuild
index 5b3c57a09..1936edf89 100755
--- a/tordnscrypt/libs/prebuild
+++ b/tordnscrypt/libs/prebuild
@@ -15,7 +15,7 @@ xz_version=v5.2.4
tor_version=prod
i2pd_openssl_version=openssl-3.3.1
miniupnpc_version=miniupnpc_2_2_8
-i2pd_version=2.53.1
+i2pd_version=2.54.0
git clone --single-branch --branch $lyrebird_version https://gitlab.torproject.org/Gedsh/lyrebird
diff --git a/tordnscrypt/owner.gradle b/tordnscrypt/owner.gradle
index bde07b063..db8f28d36 100644
--- a/tordnscrypt/owner.gradle
+++ b/tordnscrypt/owner.gradle
@@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
+ id "kotlin-parcelize"
}
def keystorePropertiesFile = file("/home/alexander/.local/KStore/keystore.properties")
@@ -34,7 +35,7 @@ android {
productFlavors {
lite {
applicationId "pan.alexander.tordnscrypt.stable"
- versionName "6.8.0"
+ versionName "6.9.0"
dimension = 'version'
signingConfig signingConfigs.stablesign
resValue 'string', 'package_name', applicationId
@@ -42,7 +43,7 @@ android {
pro {
applicationId "pan.alexander.tordnscrypt.stable"
- versionName "6.8.0"
+ versionName "6.9.0"
dimension = 'version'
signingConfig signingConfigs.stablesign
resValue 'string', 'package_name', applicationId
@@ -50,7 +51,7 @@ android {
beta {
applicationId "pan.alexander.tordnscrypt"
- versionName "2.3.2"
+ versionName "2.3.7"
dimension = 'version'
signingConfig signingConfigs.betasign
resValue 'string', 'package_name', applicationId
@@ -59,7 +60,7 @@ android {
google_play {
minSdkVersion 22
applicationId "pan.alexander.tordnscrypt.gp"
- versionName "6.8.0"
+ versionName "6.9.0"
dimension = 'version'
signingConfig signingConfigs.stablesign
resValue 'string', 'package_name', applicationId
@@ -91,7 +92,7 @@ android {
defaultConfig {
minSdkVersion 19
targetSdkVersion 34
- versionCode 232
+ versionCode 237
resConfigs "en", "ru", "pl", "de", "fa", "fi", "in", "fr", "ja", "zh", "es", "pt", "pt-rBR", "el", "tr", "it", "uk", "bg", "ar"
@@ -167,7 +168,7 @@ android {
}
}
- ndkVersion '23.0.7421159'
+ ndkVersion '23.1.7779620'
buildFeatures {
viewBinding true
@@ -200,9 +201,9 @@ dependencies {
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
- implementation 'androidx.lifecycle:lifecycle-process:2.8.4'
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.6'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6'
google_playImplementation 'com.android.billingclient:billing:6.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
@@ -210,7 +211,7 @@ dependencies {
implementation 'eu.chainfire:libsuperuser:1.1.1'
implementation 'com.jrummyapps:android-shell:1.0.1'
implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
implementation project(':filepicker')
//DI
diff --git a/tordnscrypt/src/fdroid/AndroidManifest.xml b/tordnscrypt/src/fdroid/AndroidManifest.xml
index f4b886b34..315350a9a 100644
--- a/tordnscrypt/src/fdroid/AndroidManifest.xml
+++ b/tordnscrypt/src/fdroid/AndroidManifest.xml
@@ -223,6 +223,10 @@
android:name=".settings.firewall.FirewallNotification"
android:exported="false" />
+
+
diff --git a/tordnscrypt/src/google_play/AndroidManifest.xml b/tordnscrypt/src/google_play/AndroidManifest.xml
index c5378a506..1140f899e 100644
--- a/tordnscrypt/src/google_play/AndroidManifest.xml
+++ b/tordnscrypt/src/google_play/AndroidManifest.xml
@@ -219,6 +219,10 @@
android:name=".settings.firewall.FirewallNotification"
android:exported="false" />
+
+
diff --git a/tordnscrypt/src/main/AndroidManifest.xml b/tordnscrypt/src/main/AndroidManifest.xml
index 3cd04ef2b..9d78d1120 100644
--- a/tordnscrypt/src/main/AndroidManifest.xml
+++ b/tordnscrypt/src/main/AndroidManifest.xml
@@ -206,6 +206,10 @@
android:name=".settings.firewall.FirewallNotification"
android:exported="false" />
+
+
diff --git a/tordnscrypt/src/main/assets/dnscrypt.mp3 b/tordnscrypt/src/main/assets/dnscrypt.mp3
index 682339da2..97c82bbaa 100644
Binary files a/tordnscrypt/src/main/assets/dnscrypt.mp3 and b/tordnscrypt/src/main/assets/dnscrypt.mp3 differ
diff --git a/tordnscrypt/src/main/assets/tor.mp3 b/tordnscrypt/src/main/assets/tor.mp3
index 1f5267fb2..ee19830ea 100644
Binary files a/tordnscrypt/src/main/assets/tor.mp3 and b/tordnscrypt/src/main/assets/tor.mp3 differ
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java
index eccf1c5b4..d84e0594c 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/MainActivity.java
@@ -87,6 +87,7 @@
import pan.alexander.tordnscrypt.modules.ModulesRestarter;
import pan.alexander.tordnscrypt.modules.ModulesService;
import pan.alexander.tordnscrypt.modules.ModulesStatus;
+import pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster;
import pan.alexander.tordnscrypt.settings.PathVars;
import pan.alexander.tordnscrypt.itpd_fragment.ITPDRunFragment;
import pan.alexander.tordnscrypt.settings.SettingsActivity;
@@ -105,9 +106,17 @@
import static pan.alexander.tordnscrypt.assistance.AccelerateDevelop.accelerated;
import static pan.alexander.tordnscrypt.main_fragment.ViewPagerAdapter.MAIN_SCREEN_FRAGMENT_QUANTITY;
+import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.FIREWALL;
+import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.MODULE_ARG;
+import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_ARG;
+import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_RUNNING;
+import static pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster.STATUS_STOPPED;
import static pan.alexander.tordnscrypt.utils.Utils.isInterfaceLocked;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIX_TTL;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.MAIN_ACTIVITY_RECREATE;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OPERATION_MODE;
@@ -400,6 +409,8 @@ public boolean onPrepareOptionsMenu(Menu menu) {
showNewTorIdentityIcon(menu);
+ manageFirewallIcon(menu);
+
return super.onPrepareOptionsMenu(menu);
}
@@ -463,7 +474,7 @@ private void switchIconsDependingOnMode(Menu menu, boolean rootIsAvailable) {
} else if (mode == PROXY_MODE) {
rootIcon.setIcon(R.drawable.ic_warning_white_24dp);
} else {
- rootIcon.setIcon(R.drawable.ic_vpn_key_white_24dp);
+ rootIcon.setIcon(R.drawable.ic_vpn_key);
}
if (mode == ROOT_MODE) {
@@ -500,7 +511,7 @@ private void switchIconsDependingOnMode(Menu menu, boolean rootIsAvailable) {
} else if (mode == PROXY_MODE) {
rootIcon.setIcon(R.drawable.ic_warning_white_24dp);
} else {
- rootIcon.setIcon(R.drawable.ic_vpn_key_white_24dp);
+ rootIcon.setIcon(R.drawable.ic_vpn_key);
}
hotSpot.setVisible(false);
@@ -587,6 +598,25 @@ public void showNewTorIdentityIcon(boolean show) {
invalidateMenu();
}
+ private void manageFirewallIcon(Menu menu) {
+ MenuItem firewallMenuItem = menu.findItem(R.id.menu_item_firewall);
+ if (firewallMenuItem == null) {
+ return;
+ }
+ boolean firewallIsEnabled = preferenceRepository.get().getBoolPreference(FIREWALL_ENABLED)
+ && preferenceRepository.get().getBoolPreference(FIREWALL_WAS_STARTED);
+ if (modulesStatus.getMode() == PROXY_MODE) {
+ firewallMenuItem.setVisible(false);
+ } else {
+ firewallMenuItem.setVisible(firewallIsEnabled);
+ if (modulesStatus.getFirewallState() == RUNNING) {
+ firewallMenuItem.setIcon(R.drawable.ic_firewall_active_menu);
+ } else {
+ firewallMenuItem.setIcon(R.drawable.ic_firewall_menu);
+ }
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
@@ -614,6 +644,8 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switchToVPNMode(item);
} else if (id == R.id.menu_proxies_mode) {
switchToProxyMode(item);
+ } else if (id == R.id.menu_item_firewall) {
+ showFirewallScreen();
}
return super.onOptionsItemSelected(item);
}
@@ -868,9 +900,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
intent.setAction("common_Pref");
startActivity(intent);
} else if (id == R.id.nav_firewall) {
- Intent intent = new Intent(this, SettingsActivity.class);
- intent.setAction("firewall");
- startActivity(intent);
+ showFirewallScreen();
} else if (id == R.id.nav_help) {
Intent intent = new Intent(this, HelpActivity.class);
startActivity(intent);
@@ -904,6 +934,12 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return true;
}
+ private void showFirewallScreen() {
+ Intent intent = new Intent(this, SettingsActivity.class);
+ intent.setAction("firewall");
+ startActivity(intent);
+ }
+
private void showInfoAboutRoot() {
boolean rootIsAvailable = preferenceRepository.get().getBoolPreference(ROOT_IS_AVAILABLE);
boolean busyBoxIsAvailable = preferenceRepository.get().getBoolPreference("bbOK");
@@ -1008,14 +1044,25 @@ private void registerBroadcastReceiver() {
mainActivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+
+ if (intent == null) return;
+
if (ArpScannerKt.MITM_ATTACK_WARNING.equals(intent.getAction())) {
invalidateMenu();
+ } else if (ModulesStatusBroadcaster.STATUS_ACTION.equals(intent.getAction())
+ && FIREWALL.equals(intent.getStringExtra(MODULE_ARG))) {
+ invalidateMenu();
+ if (mainFragment != null) {
+ mainFragment.refreshStartButtonText();
+ }
}
}
};
IntentFilter mitmDetected = new IntentFilter(ArpScannerKt.MITM_ATTACK_WARNING);
LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityReceiver, mitmDetected);
+ IntentFilter firewallState = new IntentFilter(ModulesStatusBroadcaster.STATUS_ACTION);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityReceiver, firewallState);
}
private void unregisterBroadcastReceiver() {
@@ -1047,7 +1094,8 @@ protected void onStop() {
if (ModulesService.serviceIsRunning && modulesStatus.getMode() == VPN_MODE
&& (modulesStatus.getDnsCryptState() == STOPPED || modulesStatus.getDnsCryptState() == FAULT || modulesStatus.getDnsCryptState() == ModuleState.UNDEFINED)
&& (modulesStatus.getTorState() == STOPPED || modulesStatus.getTorState() == FAULT || modulesStatus.getTorState() == ModuleState.UNDEFINED)
- && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == FAULT || modulesStatus.getItpdState() == ModuleState.UNDEFINED)) {
+ && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == FAULT || modulesStatus.getItpdState() == ModuleState.UNDEFINED)
+ && (modulesStatus.getFirewallState() == STOPPING || modulesStatus.getFirewallState() == STOPPED)) {
ModulesAux.stopModulesService(this);
}
@@ -1120,8 +1168,9 @@ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
@Override
public void invalidateMenu() {
- if (handler != null) {
- handler.post(this::invalidateOptionsMenu);
+ Toolbar toolbar = getWindow().findViewById(R.id.toolbar);
+ if (toolbar != null) {
+ toolbar.post(this::invalidateOptionsMenu);
}
}
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt
new file mode 100644
index 000000000..e8d108006
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSource.kt
@@ -0,0 +1,84 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.data.dns_rules
+
+import java.io.InputStreamReader
+
+interface DnsRulesDataSource {
+ fun getBlacklistRulesStream(): InputStreamReader
+ fun getSingleBlacklistRulesStream(): InputStreamReader?
+ fun saveSingleBlacklistRules(rules: List)
+ fun getRemoteBlacklistRulesStream(): InputStreamReader
+ fun getRemoteBlacklistRulesFileSize(): Long
+ fun getRemoteBlacklistRulesFileDate(): Long
+ fun clearRemoteBlacklistRules()
+ fun getLocalBlacklistRulesStream(): InputStreamReader
+ fun clearLocalBlacklistRules()
+ fun getLocalBlacklistRulesFileSize(): Long
+ fun getLocalBlacklistRulesFileDate(): Long
+
+ fun getWhitelistRulesStream(): InputStreamReader
+ fun getSingleWhitelistRulesStream(): InputStreamReader?
+ fun saveSingleWhitelistRules(rules: List)
+ fun getRemoteWhitelistRulesStream(): InputStreamReader
+ fun getRemoteWhitelistRulesFileSize(): Long
+ fun getRemoteWhitelistRulesFileDate(): Long
+ fun clearRemoteWhitelistRules()
+ fun getLocalWhitelistRulesStream(): InputStreamReader
+ fun getLocaleWhitelistRulesFileSize(): Long
+ fun getLocalWhitelistRulesFileDate(): Long
+ fun clearLocalWhitelistRules()
+
+ fun getIpBlacklistRulesStream(): InputStreamReader
+ fun getSingleIpBlacklistRulesStream(): InputStreamReader?
+ fun saveSingleIpBlacklistRules(rules: List)
+ fun getRemoteIpBlacklistRulesStream(): InputStreamReader
+ fun getRemoteIpBlacklistRulesFileSize(): Long
+ fun getRemoteIpBlacklistRulesFileDate(): Long
+ fun clearRemoteIpBlacklistRules()
+ fun getLocalIpBlacklistRulesStream(): InputStreamReader
+ fun getLocalIpBlacklistRulesFileSize(): Long
+ fun getLocalIpBlacklistRulesFileDate(): Long
+ fun clearLocalIpBlacklistRules()
+
+ fun getForwardingRulesStream(): InputStreamReader
+ fun getSingleForwardingRulesStream(): InputStreamReader?
+ fun saveSingleForwardingRules(rules: List)
+ fun getRemoteForwardingRulesStream(): InputStreamReader
+ fun getRemoteForwardingRulesFileSize(): Long
+ fun getRemoteForwardingRulesFileDate(): Long
+ fun clearRemoteForwardingRules()
+ fun getLocalForwardingRulesStream(): InputStreamReader
+ fun getLocalForwardingRulesFileSize(): Long
+ fun getLocalForwardingRulesFileDate(): Long
+ fun clearLocalForwardingRules()
+
+ fun getCloakingRulesStream(): InputStreamReader
+ fun getSingleCloakingRulesStream(): InputStreamReader?
+ fun saveSingleCloakingRules(rules: List)
+ fun getRemoteCloakingRulesStream(): InputStreamReader
+ fun getRemoteCloakingRulesFileSize(): Long
+ fun getRemoteCloakingRulesFileDate(): Long
+ fun clearRemoteCloakingRules()
+ fun getLocalCloakingRulesStream(): InputStreamReader
+ fun getLocalCloakingRulesFileSize(): Long
+ fun getLocalCloakingRulesFileDate(): Long
+ fun clearLocalCloakingRules()
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt
new file mode 100644
index 000000000..b678e5806
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesDataSourceImpl.kt
@@ -0,0 +1,309 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.data.dns_rules
+
+import android.content.Context
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.utils.filemanager.FileManager
+import java.io.File
+import java.io.InputStreamReader
+import javax.inject.Inject
+
+class DnsRulesDataSourceImpl @Inject constructor(
+ private val context: Context,
+ private val pathVars: PathVars
+) : DnsRulesDataSource {
+
+ override fun getBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptBlackListPath)
+ }
+
+ override fun getSingleBlacklistRulesStream(): InputStreamReader? {
+ val file = File(pathVars.dnsCryptSingleBlackListPath)
+ if (file.isFile) {
+ return file.reader()
+ }
+ return null
+ }
+
+ override fun saveSingleBlacklistRules(rules: List) {
+ FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleBlackListPath, rules)
+ }
+
+ override fun getRemoteBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptRemoteBlackListPath)
+ }
+
+ override fun getRemoteBlacklistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptRemoteBlackListPath).length()
+ }
+
+ override fun getRemoteBlacklistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptRemoteBlackListPath).lastModified()
+ }
+
+ override fun clearRemoteBlacklistRules() {
+ File(pathVars.dnsCryptRemoteBlackListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptLocalBlackListPath)
+ }
+
+ override fun clearLocalBlacklistRules() {
+ File(pathVars.dnsCryptLocalBlackListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalBlacklistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptLocalBlackListPath).length()
+ }
+
+ override fun getLocalBlacklistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptLocalBlackListPath).lastModified()
+ }
+
+ override fun getWhitelistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptWhiteListPath)
+ }
+
+ override fun getSingleWhitelistRulesStream(): InputStreamReader? {
+ val file = File(pathVars.dnsCryptSingleWhiteListPath)
+ if (file.isFile) {
+ return file.reader()
+ }
+ return null
+ }
+
+ override fun saveSingleWhitelistRules(rules: List) {
+ FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleWhiteListPath, rules)
+ }
+
+ override fun getRemoteWhitelistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptRemoteWhiteListPath)
+ }
+
+ override fun getRemoteWhitelistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptRemoteWhiteListPath).length()
+ }
+
+ override fun getRemoteWhitelistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptRemoteWhiteListPath).lastModified()
+ }
+
+ override fun clearRemoteWhitelistRules() {
+ File(pathVars.dnsCryptRemoteWhiteListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalWhitelistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptLocalWhiteListPath)
+ }
+
+ override fun getLocaleWhitelistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptLocalWhiteListPath).length()
+ }
+
+ override fun getLocalWhitelistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptLocalWhiteListPath).lastModified()
+ }
+
+ override fun clearLocalWhitelistRules() {
+ File(pathVars.dnsCryptLocalWhiteListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getIpBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptIPBlackListPath)
+ }
+
+ override fun getSingleIpBlacklistRulesStream(): InputStreamReader? {
+ val file = File(pathVars.dnsCryptSingleIPBlackListPath)
+ if (file.isFile) {
+ return file.reader()
+ }
+ return null
+ }
+
+ override fun saveSingleIpBlacklistRules(rules: List) {
+ FileManager.writeTextFileSynchronous(context, pathVars.dnsCryptSingleIPBlackListPath, rules)
+ }
+
+ override fun getRemoteIpBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptRemoteIPBlackListPath)
+ }
+
+ override fun getRemoteIpBlacklistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptRemoteIPBlackListPath).length()
+ }
+
+ override fun getRemoteIpBlacklistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptRemoteIPBlackListPath).lastModified()
+ }
+
+ override fun clearRemoteIpBlacklistRules() {
+ File(pathVars.dnsCryptRemoteIPBlackListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalIpBlacklistRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptLocalIPBlackListPath)
+ }
+
+ override fun getLocalIpBlacklistRulesFileSize(): Long {
+ return File(pathVars.dnsCryptLocalIPBlackListPath).length()
+ }
+
+ override fun getLocalIpBlacklistRulesFileDate(): Long {
+ return File(pathVars.dnsCryptLocalIPBlackListPath).lastModified()
+ }
+
+ override fun clearLocalIpBlacklistRules() {
+ File(pathVars.dnsCryptLocalIPBlackListPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getForwardingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptForwardingRulesPath)
+ }
+
+ override fun getSingleForwardingRulesStream(): InputStreamReader? {
+ val file = File(pathVars.dnsCryptSingleForwardingRulesPath)
+ if (file.isFile) {
+ return file.reader()
+ }
+ return null
+ }
+
+ override fun saveSingleForwardingRules(rules: List) {
+ FileManager.writeTextFileSynchronous(
+ context,
+ pathVars.dnsCryptSingleForwardingRulesPath,
+ rules
+ )
+ }
+
+ override fun getRemoteForwardingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptRemoteForwardingRulesPath)
+ }
+
+ override fun getRemoteForwardingRulesFileSize(): Long {
+ return File(pathVars.dnsCryptRemoteForwardingRulesPath).length()
+ }
+
+ override fun getRemoteForwardingRulesFileDate(): Long {
+ return File(pathVars.dnsCryptRemoteForwardingRulesPath).lastModified()
+ }
+
+ override fun clearRemoteForwardingRules() {
+ File(pathVars.dnsCryptRemoteForwardingRulesPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalForwardingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptLocalForwardingRulesPath)
+ }
+
+ override fun getLocalForwardingRulesFileSize(): Long {
+ return File(pathVars.dnsCryptLocalForwardingRulesPath).length()
+ }
+
+ override fun getLocalForwardingRulesFileDate(): Long {
+ return File(pathVars.dnsCryptLocalForwardingRulesPath).lastModified()
+ }
+
+ override fun clearLocalForwardingRules() {
+ File(pathVars.dnsCryptLocalForwardingRulesPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getCloakingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptCloakingRulesPath)
+ }
+
+ override fun getSingleCloakingRulesStream(): InputStreamReader? {
+ val file = File(pathVars.dnsCryptSingleCloakingRulesPath)
+ if (file.isFile) {
+ return file.reader()
+ }
+ return null
+ }
+
+ override fun saveSingleCloakingRules(rules: List) {
+ FileManager.writeTextFileSynchronous(
+ context,
+ pathVars.dnsCryptSingleCloakingRulesPath,
+ rules
+ )
+ }
+
+ override fun getRemoteCloakingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptRemoteCloakingRulesPath)
+ }
+
+ override fun getRemoteCloakingRulesFileSize(): Long {
+ return File(pathVars.dnsCryptRemoteCloakingRulesPath).length()
+ }
+
+ override fun getRemoteCloakingRulesFileDate(): Long {
+ return File(pathVars.dnsCryptRemoteCloakingRulesPath).lastModified()
+ }
+
+ override fun clearRemoteCloakingRules() {
+ File(pathVars.dnsCryptRemoteCloakingRulesPath).printWriter().use {
+ println()
+ }
+ }
+
+ override fun getLocalCloakingRulesStream(): InputStreamReader {
+ return getInputStreamFromFile(pathVars.dnsCryptLocalCloakingRulesPath)
+ }
+
+ override fun getLocalCloakingRulesFileSize(): Long {
+ return File(pathVars.dnsCryptLocalCloakingRulesPath).length()
+ }
+
+ override fun getLocalCloakingRulesFileDate(): Long {
+ return File(pathVars.dnsCryptLocalCloakingRulesPath).lastModified()
+ }
+
+ override fun clearLocalCloakingRules() {
+ File(pathVars.dnsCryptLocalCloakingRulesPath).printWriter().use {
+ println()
+ }
+ }
+
+ private fun getInputStreamFromFile(path: String): InputStreamReader {
+ val file = File(path)
+ if (!file.isFile) {
+ file.createNewFile()
+ }
+ return file.reader()
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt
new file mode 100644
index 000000000..4b4e7f95e
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/data/dns_rules/DnsRulesRepositoryImpl.kt
@@ -0,0 +1,431 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.data.dns_rules
+
+import kotlinx.coroutines.isActive
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesMetadata
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository.Companion.LOCAL_RULES_DEFAULT_HEADER
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository.Companion.REMOTE_RULES_DEFAULT_HEADER
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.util.Date
+import java.util.regex.Pattern
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+
+class DnsRulesRepositoryImpl @Inject constructor(
+ private val dataSource: DnsRulesDataSource,
+ pathVars: PathVars
+) : DnsRulesRepository {
+
+ private val forwardingDefaultRule = pathVars.dnsCryptDefaultForwardingRule
+ .substringBefore(" ")
+ private val cloakingDefaultRule = pathVars.dnsCryptDefaultCloakingRule
+ .substringBefore(" ")
+
+ override suspend fun getMixedBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata =
+ DnsRulesMetadata.MixedDnsRulesMetadata(
+ getRulesCountFromFile(dataSource.getBlacklistRulesStream())
+ )
+
+ override suspend fun getSingleBlacklistRules(): List =
+ getSingleRulesFromFile(
+ dataSource.getSingleBlacklistRulesStream() ?: run {
+ val rules = if (getMixedBlacklistRulesMetadata().count > 0
+ && getLocalBlacklistRulesMetadata().count == 0
+ ) {
+ getSingleRulesFromFile(dataSource.getBlacklistRulesStream()).also {
+ clearLocalBlacklistRules()
+ }
+ } else {
+ emptyList()
+ }
+ saveSingleBlacklistRules(rules)
+ dataSource.getSingleBlacklistRulesStream() ?: throw IllegalStateException(
+ "DnsRulesRepository getSingleBlacklistRules"
+ )
+ }
+ )
+
+ override fun saveSingleBlacklistRules(rules: List) {
+ dataSource.saveSingleBlacklistRules(mapSingleRulesToLines(rules))
+ }
+
+ override suspend fun getRemoteBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata {
+ val url = getHeaderFromFile(dataSource.getRemoteBlacklistRulesStream())
+ ?: REMOTE_RULES_DEFAULT_HEADER
+ return DnsRulesMetadata.RemoteDnsRulesMetadata(
+ name = getDomainNameFromUrl(url),
+ url = url,
+ date = Date(dataSource.getRemoteBlacklistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getRemoteBlacklistRulesStream()),
+ size = dataSource.getRemoteBlacklistRulesFileSize()
+ )
+ }
+
+ override fun clearRemoteBlacklistRules() {
+ dataSource.clearRemoteBlacklistRules()
+ }
+
+ override fun clearLocalBlacklistRules() {
+ dataSource.clearLocalBlacklistRules()
+ }
+
+ override suspend fun getLocalBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata =
+ DnsRulesMetadata.LocalDnsRulesMetadata(
+ name = getHeaderFromFile(dataSource.getLocalBlacklistRulesStream())
+ ?: LOCAL_RULES_DEFAULT_HEADER,
+ date = Date(dataSource.getLocalBlacklistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getLocalBlacklistRulesStream()),
+ size = dataSource.getLocalBlacklistRulesFileSize()
+ )
+
+ override suspend fun getMixedWhitelistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata =
+ DnsRulesMetadata.MixedDnsRulesMetadata(
+ getRulesCountFromFile(dataSource.getWhitelistRulesStream())
+ )
+
+ override suspend fun getSingleWhitelistRules(): List =
+ getSingleRulesFromFile(
+ dataSource.getSingleWhitelistRulesStream() ?: run {
+ val rules = if (getMixedWhitelistRulesMetadata().count > 0
+ && getLocalWhitelistRulesMetadata().count == 0
+ ) {
+ getSingleRulesFromFile(dataSource.getWhitelistRulesStream()).also {
+ clearLocalWhitelistRules()
+ }
+ } else {
+ emptyList()
+ }
+ saveSingleWhitelistRules(rules)
+ dataSource.getSingleWhitelistRulesStream() ?: throw IllegalStateException(
+ "DnsRulesRepository getSingleWhitelistRules"
+ )
+ }
+ )
+
+ override fun saveSingleWhitelistRules(rules: List) {
+ dataSource.saveSingleWhitelistRules(mapSingleRulesToLines(rules))
+ }
+
+ override suspend fun getRemoteWhitelistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata {
+ val url = getHeaderFromFile(dataSource.getRemoteWhitelistRulesStream())
+ ?: REMOTE_RULES_DEFAULT_HEADER
+ return DnsRulesMetadata.RemoteDnsRulesMetadata(
+ name = getDomainNameFromUrl(url),
+ url = url,
+ date = Date(dataSource.getRemoteWhitelistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getRemoteWhitelistRulesStream()),
+ size = dataSource.getRemoteWhitelistRulesFileSize()
+ )
+ }
+
+ override fun clearRemoteWhitelistRules() {
+ dataSource.clearRemoteWhitelistRules()
+ }
+
+ override fun clearLocalWhitelistRules() {
+ dataSource.clearLocalWhitelistRules()
+ }
+
+ override suspend fun getLocalWhitelistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata =
+ DnsRulesMetadata.LocalDnsRulesMetadata(
+ name = getHeaderFromFile(dataSource.getLocalWhitelistRulesStream())
+ ?: LOCAL_RULES_DEFAULT_HEADER,
+ date = Date(dataSource.getLocalWhitelistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getLocalWhitelistRulesStream()),
+ size = dataSource.getLocaleWhitelistRulesFileSize()
+ )
+
+ override suspend fun getMixedIpBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata =
+ DnsRulesMetadata.MixedDnsRulesMetadata(
+ getRulesCountFromFile(dataSource.getIpBlacklistRulesStream())
+ )
+
+ override suspend fun getSingleIpBlacklistRules(): List =
+ getSingleRulesFromFile(
+ dataSource.getSingleIpBlacklistRulesStream() ?: run {
+ val rules = if (getMixedIpBlacklistRulesMetadata().count > 0
+ && getLocalIpBlacklistRulesMetadata().count == 0
+ ) {
+ getSingleRulesFromFile(dataSource.getIpBlacklistRulesStream()).also {
+ clearLocalIpBlacklistRules()
+ }
+ } else {
+ emptyList()
+ }
+ saveSingleIpBlacklistRules(rules)
+ dataSource.getSingleIpBlacklistRulesStream() ?: throw IllegalStateException(
+ "DnsRulesRepository getSingleIpBlacklistRules"
+ )
+ }
+ )
+
+ override fun saveSingleIpBlacklistRules(rules: List) {
+ dataSource.saveSingleIpBlacklistRules(mapSingleRulesToLines(rules))
+ }
+
+ override suspend fun getRemoteIpBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata {
+ val url = getHeaderFromFile(dataSource.getRemoteIpBlacklistRulesStream())
+ ?: REMOTE_RULES_DEFAULT_HEADER
+ return DnsRulesMetadata.RemoteDnsRulesMetadata(
+ name = getDomainNameFromUrl(url),
+ url = url,
+ date = Date(dataSource.getRemoteIpBlacklistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getRemoteIpBlacklistRulesStream()),
+ size = dataSource.getRemoteIpBlacklistRulesFileSize()
+ )
+ }
+
+ override fun clearRemoteIpBlacklistRules() {
+ dataSource.clearRemoteIpBlacklistRules()
+ }
+
+ override fun clearLocalIpBlacklistRules() {
+ dataSource.clearLocalIpBlacklistRules()
+ }
+
+ override suspend fun getLocalIpBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata =
+ DnsRulesMetadata.LocalDnsRulesMetadata(
+ name = getHeaderFromFile(dataSource.getLocalIpBlacklistRulesStream())
+ ?: LOCAL_RULES_DEFAULT_HEADER,
+ date = Date(dataSource.getLocalIpBlacklistRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getLocalIpBlacklistRulesStream()),
+ size = dataSource.getLocalIpBlacklistRulesFileSize()
+ )
+
+ override suspend fun getMixedForwardingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata =
+ DnsRulesMetadata.MixedDnsRulesMetadata(
+ getRulesCountFromFile(dataSource.getForwardingRulesStream())
+ )
+
+ override suspend fun getSingleForwardingRules(): List =
+ getSingleRulesFromFile(
+ dataSource.getSingleForwardingRulesStream() ?: run {
+ val rules = if (getMixedForwardingRulesMetadata().count > 0
+ && getLocalForwardingRulesMetadata().count == 1
+ ) {
+ getSingleRulesFromFile(dataSource.getForwardingRulesStream()).also {
+ clearLocalForwardingRules()
+ }
+ } else {
+ listOf(
+ DnsRuleRecycleItem.DnsSingleRule(
+ rule = forwardingDefaultRule,
+ protected = true,
+ active = true
+ )
+ )
+ }
+ saveSingleForwardingRules(rules)
+ dataSource.getSingleForwardingRulesStream() ?: throw IllegalStateException(
+ "DnsRulesRepository getSingleForwardingRules"
+ )
+ }
+ )
+
+ override fun saveSingleForwardingRules(rules: List) {
+ dataSource.saveSingleForwardingRules(mapSingleRulesToLines(rules))
+ }
+
+ override suspend fun getRemoteForwardingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata {
+ val url = getHeaderFromFile(dataSource.getRemoteForwardingRulesStream())
+ ?: REMOTE_RULES_DEFAULT_HEADER
+ return DnsRulesMetadata.RemoteDnsRulesMetadata(
+ name = getDomainNameFromUrl(url),
+ url = url,
+ date = Date(dataSource.getRemoteForwardingRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getRemoteForwardingRulesStream()),
+ size = dataSource.getRemoteForwardingRulesFileSize()
+ )
+ }
+
+ override fun clearRemoteForwardingRules() {
+ dataSource.clearRemoteForwardingRules()
+ }
+
+ override fun clearLocalForwardingRules() {
+ dataSource.clearLocalForwardingRules()
+ }
+
+ override suspend fun getLocalForwardingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata =
+ DnsRulesMetadata.LocalDnsRulesMetadata(
+ name = getHeaderFromFile(dataSource.getLocalForwardingRulesStream())
+ ?: LOCAL_RULES_DEFAULT_HEADER,
+ date = Date(dataSource.getLocalForwardingRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getLocalForwardingRulesStream()),
+ size = dataSource.getLocalForwardingRulesFileSize()
+ )
+
+ override suspend fun getMixedCloakingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata =
+ DnsRulesMetadata.MixedDnsRulesMetadata(
+ getRulesCountFromFile(dataSource.getCloakingRulesStream())
+ )
+
+ override suspend fun getSingleCloakingRules(): List =
+ getSingleRulesFromFile(
+ dataSource.getSingleCloakingRulesStream() ?: run {
+ val rules = if (getMixedCloakingRulesMetadata().count > 0
+ && getLocalCloakingRulesMetadata().count == 1
+ ) {
+ getSingleRulesFromFile(dataSource.getCloakingRulesStream()).also {
+ clearLocalCloakingRules()
+ }
+ } else {
+ listOf(
+ DnsRuleRecycleItem.DnsSingleRule(
+ rule = cloakingDefaultRule,
+ protected = true,
+ active = true
+ )
+ )
+ }
+ saveSingleCloakingRules(rules)
+ dataSource.getSingleCloakingRulesStream() ?: throw IllegalStateException(
+ "DnsRulesRepository getSingleCloakingRules"
+ )
+ }
+ )
+
+ override fun saveSingleCloakingRules(rules: List) {
+ dataSource.saveSingleCloakingRules(mapSingleRulesToLines(rules))
+ }
+
+ override suspend fun getRemoteCloakingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata {
+ val url = getHeaderFromFile(dataSource.getRemoteCloakingRulesStream())
+ ?: REMOTE_RULES_DEFAULT_HEADER
+ return DnsRulesMetadata.RemoteDnsRulesMetadata(
+ name = getDomainNameFromUrl(url),
+ url = url,
+ date = Date(dataSource.getRemoteCloakingRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getRemoteCloakingRulesStream()),
+ size = dataSource.getRemoteCloakingRulesFileSize()
+ )
+ }
+
+ override fun clearRemoteCloakingRules() {
+ dataSource.clearRemoteCloakingRules()
+ }
+
+ override fun clearLocalCloakingRules() {
+ dataSource.clearLocalCloakingRules()
+ }
+
+ override suspend fun getLocalCloakingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata =
+ DnsRulesMetadata.LocalDnsRulesMetadata(
+ name = getHeaderFromFile(dataSource.getLocalCloakingRulesStream())
+ ?: LOCAL_RULES_DEFAULT_HEADER,
+ date = Date(dataSource.getLocalCloakingRulesFileDate()),
+ count = getRulesCountFromFile(dataSource.getLocalCloakingRulesStream()),
+ size = dataSource.getLocalCloakingRulesFileSize()
+ )
+
+ private fun mapLineToSingleRule(line: String): DnsRuleRecycleItem.DnsSingleRule {
+ val active = !line.startsWith("#")
+ val protected = if (active) {
+ line.startsWith(forwardingDefaultRule.substringBefore(" "))
+ || line.startsWith(cloakingDefaultRule.substringBefore(" "))
+ } else {
+ line.startsWith("#${forwardingDefaultRule.substringBefore(" ")}")
+ || line.startsWith("#${cloakingDefaultRule.substringBefore(" ")}")
+ }
+ return DnsRuleRecycleItem.DnsSingleRule(
+ line.removePrefix("#"),
+ protected,
+ active
+ )
+ }
+
+ private fun mapSingleRulesToLines(rules: List): List {
+ val lines = mutableListOf()
+ for (rule in rules) {
+
+ if (rule.rule.isEmpty()) {
+ continue
+ }
+
+ val line = if (rule.active) {
+ rule.rule
+ } else {
+ "#${rule.rule}"
+ }
+ lines.add(line)
+ }
+ return lines
+ }
+
+ private suspend fun getHeaderFromFile(inputReader: InputStreamReader): String? {
+ val namePattern = Pattern.compile("^# ([^#]+) #$")
+ BufferedReader(inputReader).use { reader ->
+ var line = reader.readLine()
+ while (line != null && coroutineContext.isActive) {
+ if (line.startsWith("#")) {
+ val matcher = namePattern.matcher(line)
+ if (matcher.matches()) {
+ matcher.group(1)?.let {
+ return it
+ }
+ }
+ } else if (line.isNotEmpty()) {
+ return null
+ }
+ line = reader.readLine()
+ }
+ }
+ return null
+ }
+
+ private suspend fun getRulesCountFromFile(inputReader: InputStreamReader): Int {
+ var count = 0
+ BufferedReader(inputReader).use { reader ->
+ var line = reader.readLine()
+ while (line != null && coroutineContext.isActive) {
+ if (line.isNotEmpty() && !line.startsWith("#")) {
+ count++
+ }
+ line = reader.readLine()
+ }
+ }
+ return count
+ }
+
+ private suspend fun getSingleRulesFromFile(
+ inputReader: InputStreamReader
+ ): List {
+ val rules = mutableListOf()
+ BufferedReader(inputReader).use { reader ->
+ var line = reader.readLine()
+ while (line != null && coroutineContext.isActive) {
+ if (line.isNotEmpty()
+ && !line.startsWith("##")
+ && !(line.startsWith("#") && line.endsWith("#"))
+ ) {
+ rules += mapLineToSingleRule(line)
+ }
+ line = reader.readLine()
+ }
+ }
+ return rules
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt
index bcc33d221..60ed98e69 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/AppComponent.kt
@@ -49,8 +49,12 @@ import pan.alexander.tordnscrypt.proxy.ProxyFragment
import pan.alexander.tordnscrypt.settings.*
import pan.alexander.tordnscrypt.settings.dnscrypt_relays.PreferencesDNSCryptRelays
import pan.alexander.tordnscrypt.settings.dnscrypt_servers.PreferencesDNSCryptServers
+import pan.alexander.tordnscrypt.settings.dnscrypt_settings.RulesEraser
import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment
import pan.alexander.tordnscrypt.settings.firewall.FirewallFragment
+import pan.alexander.tordnscrypt.settings.itpd_settings.PreferencesITPDFragment
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesFragment
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalDnsRulesWorker
import pan.alexander.tordnscrypt.settings.tor_apps.UnlockTorAppsFragment
import pan.alexander.tordnscrypt.settings.tor_bridges.BridgeAdapter
import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridges
@@ -67,6 +71,9 @@ import pan.alexander.tordnscrypt.utils.filemanager.FileManager
import pan.alexander.tordnscrypt.utils.integrity.Verifier
import pan.alexander.tordnscrypt.utils.root.RootExecService
import pan.alexander.tordnscrypt.utils.web.TorRefreshIPsWork
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteDnsRulesWorker
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingDnsRulesWorker
+import pan.alexander.tordnscrypt.settings.itpd_settings.ITPDSubscriptionsFragment
import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHandler
import javax.inject.Singleton
@@ -114,8 +121,10 @@ interface AppComponent {
fun inject(fragment: PreferencesITPDFragment)
fun inject(fragment: PreferencesDNSCryptRelays)
fun inject(fragment: PreferencesDNSFragment)
+ fun inject(fragment: ITPDSubscriptionsFragment)
fun inject(fragment: UpdateModulesDialogFragment)
fun inject(fragment: NotificationHelper)
+ fun inject(fragment: DnsRulesFragment)
fun inject(service: ModulesService)
fun inject(service: RootExecService)
fun inject(service: UpdateService)
@@ -150,4 +159,8 @@ interface AppComponent {
fun inject(installer: Installer)
fun inject(installedApplicationsManager: InstalledApplicationsManager)
fun inject(agreementDialog: AgreementDialog)
+ fun inject(rulesEraser: RulesEraser)
+ fun inject(worker: UpdateRemoteDnsRulesWorker)
+ fun inject(worker: UpdateLocalDnsRulesWorker)
+ fun inject(worker: RemixExistingDnsRulesWorker)
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt
index 19d6f84dc..5efcf8e19 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/DataSourcesModule.kt
@@ -26,6 +26,8 @@ import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerDataSo
import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerDataSourceImpl
import pan.alexander.tordnscrypt.data.dns_resolver.DnsDataSource
import pan.alexander.tordnscrypt.data.dns_resolver.DnsDataSourceImpl
+import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesDataSource
+import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesDataSourceImpl
import pan.alexander.tordnscrypt.data.preferences.PreferenceDataSource
import pan.alexander.tordnscrypt.data.preferences.PreferenceDataSourceImpl
@@ -60,4 +62,9 @@ abstract class DataSourcesModule {
abstract fun provideBridgesCountriesDataSource(
bridgesCountriesDataSource: BridgesCountriesDataSourceImpl
): BridgesCountriesDataSource
+
+ @Binds
+ abstract fun provideDnsRulesDataSource(
+ dnsDataSource: DnsRulesDataSourceImpl
+ ): DnsRulesDataSource
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt
index ab72ec851..16344ce3d 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/InteractorsModule.kt
@@ -25,6 +25,8 @@ import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInte
import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInteractorImpl
import pan.alexander.tordnscrypt.domain.dns_resolver.DnsInteractor
import pan.alexander.tordnscrypt.domain.dns_resolver.DnsInteractorImpl
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractor
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractorImpl
import pan.alexander.tordnscrypt.domain.tor_ips.TorIpsInteractor
import pan.alexander.tordnscrypt.domain.tor_ips.TorIpsInteractorImpl
@@ -43,4 +45,9 @@ abstract class InteractorsModule {
abstract fun provideTorIpsInteractor(
torIpsInteractor: TorIpsInteractorImpl
): TorIpsInteractor
+
+ @Binds
+ abstract fun provideDnsRulesInteractor(
+ dnsRulesInteractor: DnsRulesInteractorImpl
+ ): DnsRulesInteractor
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt
index e0d499a9a..7417c42ba 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/RepositoryModule.kt
@@ -26,6 +26,7 @@ import pan.alexander.tordnscrypt.data.bridges.DefaultVanillaBridgeRepositoryImpl
import pan.alexander.tordnscrypt.data.bridges.RequestBridgesRepositoryImpl
import pan.alexander.tordnscrypt.data.connection_checker.ConnectionCheckerRepositoryImpl
import pan.alexander.tordnscrypt.data.dns_resolver.DnsRepositoryImpl
+import pan.alexander.tordnscrypt.data.dns_rules.DnsRulesRepositoryImpl
import pan.alexander.tordnscrypt.data.preferences.PreferenceRepositoryImpl
import pan.alexander.tordnscrypt.data.resources.ResourceRepositoryImpl
import pan.alexander.tordnscrypt.domain.bridges.BridgesCountriesRepository
@@ -33,6 +34,7 @@ import pan.alexander.tordnscrypt.domain.bridges.DefaultVanillaBridgeRepository
import pan.alexander.tordnscrypt.domain.bridges.RequestBridgesRepository
import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerRepository
import pan.alexander.tordnscrypt.domain.dns_resolver.DnsRepository
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesRepository
import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
import pan.alexander.tordnscrypt.domain.resources.ResourceRepository
@@ -69,4 +71,9 @@ abstract class RepositoryModule {
abstract fun provideBridgesCountriesRepository(
bridgesCountriesRepository: BridgesCountriesRepositoryImpl
): BridgesCountriesRepository
+
+ @Binds
+ abstract fun provideDnsRulesRepository(
+ dnsRulesRepository: DnsRulesRepositoryImpl
+ ): DnsRulesRepository
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt
index 16c51faba..d601764bc 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/di/ViewModelModule.kt
@@ -30,6 +30,8 @@ import pan.alexander.tordnscrypt.TopFragmentViewModel
import pan.alexander.tordnscrypt.settings.dnscrypt_relays.DnsRelayViewModel
import pan.alexander.tordnscrypt.settings.dnscrypt_servers.DnsServerViewModel
import pan.alexander.tordnscrypt.settings.firewall.FirewallViewModel
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesViewModel
+import pan.alexander.tordnscrypt.settings.itpd_settings.ItpdSubscriptionsViewModel
import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridgesViewModel
import pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsViewModel
@@ -84,4 +86,18 @@ abstract class ViewModelModule {
abstract fun provideDnsRelayViewModel(
dnsRelayViewModel: DnsRelayViewModel
): ViewModel
+
+ @Binds
+ @IntoMap
+ @ViewModelKey(DnsRulesViewModel::class)
+ abstract fun provideDnsRuleViewModel(
+ dnsRulesViewModel: DnsRulesViewModel
+ ): ViewModel
+
+ @Binds
+ @IntoMap
+ @ViewModelKey(ItpdSubscriptionsViewModel::class)
+ abstract fun provideItpdSubscriptionsViewModel(
+ itpdSubscriptionsViewModel: ItpdSubscriptionsViewModel
+ ): ViewModel
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java
index 385b5f54a..46a6d57c0 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/ExtendedDialogFragment.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -127,7 +128,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
public void show(@NonNull FragmentManager manager, String tag) {
try {
showDialog(manager, tag);
- } catch (IllegalStateException e) {
+ } catch (IllegalStateException | WindowManager.BadTokenException e) {
logw("ExtendedDialogFragment show", e);
if (handler == null) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt
index f895af487..4766fd2d8 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SelectBridgesTransportDialogFragment.kt
@@ -36,6 +36,7 @@ import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import pan.alexander.tordnscrypt.App
import pan.alexander.tordnscrypt.R
+import pan.alexander.tordnscrypt.databinding.SelectTorTransportBinding
import pan.alexander.tordnscrypt.di.SharedPreferencesModule.Companion.DEFAULT_PREFERENCES_NAME
import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridgesViewModel
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
@@ -67,26 +68,21 @@ class SelectBridgesTransportDialogFragment : ExtendedDialogFragment() {
@SuppressLint("InflateParams")
override fun assignBuilder(): AlertDialog.Builder =
AlertDialog.Builder(requireActivity()).apply {
- val layoutInflater =
- requireActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
- val view: View = try {
- layoutInflater.inflate(R.layout.select_tor_transport, null)
+ val binding = try {
+ SelectTorTransportBinding.inflate(LayoutInflater.from(requireContext()))
} catch (e: Exception) {
loge("SelectBridgesTransportDialogFragment assignBuilder", e)
throw e
}
- val rbgTorTransport = view.findViewById(R.id.rbgTorTransport)
- val chbRequestIPv6Bridges = view.findViewById(R.id.chbRequestIPv6Bridges)
-
if (defaultPreferences.getBoolean(TOR_USE_IPV6, true)) {
- chbRequestIPv6Bridges.visibility = VISIBLE
+ binding.chbRequestIPv6Bridges.visibility = VISIBLE
} else {
- chbRequestIPv6Bridges.visibility = GONE
+ binding.chbRequestIPv6Bridges.visibility = GONE
}
- setView(view)
+ setView(binding.root)
setTitle(R.string.pref_fast_use_tor_bridges_transport_select)
@@ -94,23 +90,21 @@ class SelectBridgesTransportDialogFragment : ExtendedDialogFragment() {
okButtonPressed = true
- val ipv6Bridges = chbRequestIPv6Bridges.isChecked
-
- when (rbgTorTransport.checkedRadioButtonId) {
+ when (binding.rbgTorTransport.checkedRadioButtonId) {
R.id.rbObfsNone ->
preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge(
- "0",
- ipv6Bridges
+ "vanilla",
+ binding.chbRequestIPv6Bridges.isChecked
)
R.id.rbObfs4 ->
preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge(
"obfs4",
- ipv6Bridges
+ binding.chbRequestIPv6Bridges.isChecked
)
R.id.rbWebTunnel ->
preferencesTorBridgesViewModel.requestTorBridgesCaptchaChallenge(
"webtunnel",
- ipv6Bridges
+ binding.chbRequestIPv6Bridges.isChecked
)
}
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt
index e60afe0b0..03d09eda6 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/SendCrashReport.kt
@@ -21,6 +21,7 @@ package pan.alexander.tordnscrypt.dialogs
import android.content.Context
import android.os.Bundle
+import androidx.annotation.Keep
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.preference.PreferenceManager
@@ -41,6 +42,7 @@ import java.io.FileWriter
import java.io.InputStreamReader
import javax.inject.Inject
+@Keep
class SendCrashReport : ExtendedDialogFragment() {
@Inject
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java
deleted file mode 100644
index d10f78b99..000000000
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dialogs/progressDialogs/ImportRulesDialog.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- This file is part of InviZible Pro.
-
- InviZible Pro 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.
-
- InviZible Pro 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 InviZible Pro. If not, see .
-
- Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
- */
-
-package pan.alexander.tordnscrypt.dialogs.progressDialogs;
-
-import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
-
-import android.annotation.SuppressLint;
-import android.app.Dialog;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-
-import pan.alexander.tordnscrypt.R;
-import pan.alexander.tordnscrypt.dialogs.ExtendedDialogFragment;
-import pan.alexander.tordnscrypt.settings.dnscrypt_settings.ImportRules;
-
-public class ImportRulesDialog extends ExtendedDialogFragment implements ImportRules.OnDNSCryptRuleAddLineListener {
- private Thread importThread;
- private int linesAdded = 0;
- private String dialogMessage;
- private int buttonText = R.string.cancel;
- private boolean dialogImportRulesIndeterminate;
- private TextView tvDialogImportRules;
- private ProgressBar pbDialogImportRules;
- private Button btnDialogImportRules;
-
- public static ImportRulesDialog newInstance() {
- return new ImportRulesDialog();
- }
-
- @SuppressLint("InflateParams")
- @Override
- public AlertDialog.Builder assignBuilder() {
-
- if (getActivity() == null) {
- return null;
- }
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.import_dnscrypt_rules_dialog_title);
-
- View view;
- try {
- view = getActivity().getLayoutInflater().inflate(R.layout.dialog_import_dnscrypt_rules, null, false);
- } catch (Exception e) {
- loge("ImportRulesDialog assignBuilder", e);
- throw e;
- }
-
- if (view != null) {
- builder.setView(view);
- tvDialogImportRules = view.findViewById(R.id.tvDialogImportRules);
- pbDialogImportRules = view.findViewById(R.id.pbDialogImportRules);
- btnDialogImportRules = view.findViewById(R.id.btnDialogImportRules);
- }
-
- if (btnDialogImportRules != null) {
- btnDialogImportRules.setOnClickListener(v -> {
- if (importThread != null && importThread.isAlive()) {
- importThread.interrupt();
- }
- this.dismiss();
- });
- }
-
- builder.setCancelable(false);
- return builder;
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- Dialog dialog = super.onCreateDialog(savedInstanceState);
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (tvDialogImportRules != null && pbDialogImportRules != null && btnDialogImportRules != null) {
- if (dialogMessage != null) {
- tvDialogImportRules.setText(dialogMessage);
- }
- pbDialogImportRules.setIndeterminate(dialogImportRulesIndeterminate);
- if (dialogImportRulesIndeterminate) {
- pbDialogImportRules.setVisibility(View.VISIBLE);
- } else {
- pbDialogImportRules.setVisibility(View.GONE);
- }
- btnDialogImportRules.setText(buttonText);
- }
- }
-
- @Override
- public void onDNSCryptRuleLinesAddingStarted(@NonNull Thread importThread) {
- this.importThread = importThread;
-
- try {
- int count = 0;
- while (count < 15 && (tvDialogImportRules == null || pbDialogImportRules == null)) {
- count++;
- //noinspection BusyWait
- Thread.sleep(100);
- }
- } catch (InterruptedException ignored){}
-
- if (handler != null) {
- handler.post(() -> {
- if (tvDialogImportRules != null && pbDialogImportRules != null) {
- dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_dialog_message), linesAdded);
- tvDialogImportRules.setText(dialogMessage);
- pbDialogImportRules.setVisibility(View.VISIBLE);
- pbDialogImportRules.setIndeterminate(true);
- dialogImportRulesIndeterminate = true;
- }
- });
- }
- }
-
- @Override
- public void onDNSCryptRuleLineAdded(int count) {
- linesAdded = count;
- dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_dialog_message), linesAdded);
- if (handler != null) {
- handler.post(() -> {
- if (tvDialogImportRules != null) {
- tvDialogImportRules.setText(dialogMessage);
- }
- });
- }
- }
-
- @Override
- public void onDNSCryptRuleLinesAddingFinished() {
- if (handler != null) {
- handler.post(() -> {
- if (tvDialogImportRules!= null && pbDialogImportRules != null && btnDialogImportRules != null) {
- dialogMessage = String.format(getString(R.string.import_dnscrypt_rules_complete_dialog_message), linesAdded);
- tvDialogImportRules.setText(dialogMessage);
- pbDialogImportRules.setIndeterminate(false);
- pbDialogImportRules.setVisibility(View.GONE);
- dialogImportRulesIndeterminate = false;
- buttonText = R.string.ok;
- btnDialogImportRules.setText(buttonText);
- }
- });
- }
-
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (importThread != null && importThread.isAlive()) {
- importThread.interrupt();
- }
- }
-}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java
index d23308350..fcda84132 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/dnscrypt_fragment/DNSCryptRunFragment.java
@@ -42,14 +42,20 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.MainActivity;
import pan.alexander.tordnscrypt.R;
import pan.alexander.tordnscrypt.TopFragment;
+import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.utils.root.RootExecService;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static pan.alexander.tordnscrypt.TopFragment.DNSCryptVersion;
import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import com.google.android.material.divider.MaterialDivider;
@@ -207,10 +213,28 @@ public void onDestroyView() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnDNSCryptStart) {
+ letFirewallStop();
presenter.startButtonOnClick();
}
}
+ private void letFirewallStop() {
+ ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if (modulesStatus.getFirewallState() != STOPPED
+ && modulesStatus.getDnsCryptState() == RUNNING
+ && (modulesStatus.getTorState() == STOPPED
+ || modulesStatus.getTorState() == STOPPING
+ || modulesStatus.getTorState() == FAULT)
+ && (modulesStatus.getItpdState() == STOPPED
+ || modulesStatus.getItpdState() == STOPPING
+ || modulesStatus.getItpdState() == FAULT)) {
+ modulesStatus.setFirewallState(
+ STOPPING,
+ App.getInstance().getDaggerComponent().getPreferenceRepository().get()
+ );
+ }
+ }
+
@Override
public void setDNSCryptStatus(int resourceText, int resourceColor) {
tvDNSStatus.setText(resourceText);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt
index e273508e1..f27c6d5ed 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/bridges/RequestBridgesInteractor.kt
@@ -119,10 +119,14 @@ class RequestBridgesInteractor @Inject constructor(
lockWakeLock()
- val query = linkedMapOf().apply {
- put("captcha_challenge_field", secretCode)
- put("captcha_response_field", captchaText)
- put("submit", "submit")
+ val query = if (secretCode.isNotEmpty() && captchaText.isNotEmpty()) {
+ linkedMapOf().apply {
+ put("captcha_challenge_field", secretCode)
+ put("captcha_response_field", captchaText)
+ put("submit", "submit")
+ }
+ } else {
+ linkedMapOf()
}
val url = if (ipv6) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt
index 29ace49cc..38fee8599 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/connection_records/ConnectionRecordsConverter.kt
@@ -506,7 +506,7 @@ class ConnectionRecordsConverter @Inject constructor(
private fun updateVars() {
- iptablesFirewall.get().prepareUidAllowed()
+ iptablesFirewall.get().prepareUidAllowed(NetworkChecker.isVpnActive(context))
blockIPv6 = isBlockIPv6()
meteredNetwork = isMeteredNetwork()
vpnDNS = VpnBuilder.vpnDnsSet
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt
similarity index 82%
rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt
rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt
index 517f38e09..6506011b7 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/enums/DNSCryptRulesVariant.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRuleType.kt
@@ -17,13 +17,12 @@
Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
*/
-package pan.alexander.tordnscrypt.utils.enums
+package pan.alexander.tordnscrypt.domain.dns_rules
-enum class DNSCryptRulesVariant {
- BLACKLIST_HOSTS,
- BLACKLIST_IPS,
- WHITELIST_HOSTS,
+enum class DnsRuleType {
+ BLACKLIST,
+ WHITELIST,
+ IP_BLACKLIST,
FORWARDING,
- CLOAKING,
- UNDEFINED
-}
\ No newline at end of file
+ CLOAKING
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt
new file mode 100644
index 000000000..166d64421
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.domain.dns_rules
+
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+
+interface DnsRulesInteractor {
+
+ suspend fun getMixedRulesMetadata(type: DnsRuleType): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleRules(type: DnsRuleType): List
+ suspend fun saveSingleRules(type: DnsRuleType, rules: List)
+ suspend fun getRemoteRulesMetadata(type: DnsRuleType): DnsRulesMetadata.RemoteDnsRulesMetadata
+ suspend fun getLocalRulesMetadata(type: DnsRuleType): DnsRulesMetadata.LocalDnsRulesMetadata
+ suspend fun clearRemoteRules(type: DnsRuleType)
+ suspend fun clearLocalRules(type: DnsRuleType)
+
+ suspend fun isExternalStorageAllowsDirectAccess(): Boolean
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt
new file mode 100644
index 000000000..a8e710209
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesInteractorImpl.kt
@@ -0,0 +1,120 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.domain.dns_rules
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runInterruptible
+import kotlinx.coroutines.withContext
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+import pan.alexander.tordnscrypt.utils.Utils.isLogsDirAccessible
+import javax.inject.Inject
+import javax.inject.Named
+
+class DnsRulesInteractorImpl @Inject constructor(
+ private val repository: DnsRulesRepository,
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ private val dispatcherIo: CoroutineDispatcher
+) : DnsRulesInteractor {
+
+ override suspend fun getMixedRulesMetadata(type: DnsRuleType): DnsRulesMetadata.MixedDnsRulesMetadata =
+ withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.getMixedBlacklistRulesMetadata()
+ DnsRuleType.WHITELIST -> repository.getMixedWhitelistRulesMetadata()
+ DnsRuleType.IP_BLACKLIST -> repository.getMixedIpBlacklistRulesMetadata()
+ DnsRuleType.FORWARDING -> repository.getMixedForwardingRulesMetadata()
+ DnsRuleType.CLOAKING -> repository.getMixedCloakingRulesMetadata()
+ }
+ }
+
+ override suspend fun getSingleRules(type: DnsRuleType): List =
+ withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.getSingleBlacklistRules()
+ DnsRuleType.WHITELIST -> repository.getSingleWhitelistRules()
+ DnsRuleType.IP_BLACKLIST -> repository.getSingleIpBlacklistRules()
+ DnsRuleType.FORWARDING -> repository.getSingleForwardingRules()
+ DnsRuleType.CLOAKING -> repository.getSingleCloakingRules()
+ }
+ }
+
+ override suspend fun saveSingleRules(
+ type: DnsRuleType,
+ rules: List
+ ) = withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.saveSingleBlacklistRules(rules)
+ DnsRuleType.WHITELIST -> repository.saveSingleWhitelistRules(rules)
+ DnsRuleType.IP_BLACKLIST -> repository.saveSingleIpBlacklistRules(rules)
+ DnsRuleType.FORWARDING -> repository.saveSingleForwardingRules(rules)
+ DnsRuleType.CLOAKING -> repository.saveSingleCloakingRules(rules)
+ }
+ }
+
+ override suspend fun getRemoteRulesMetadata(
+ type: DnsRuleType
+ ): DnsRulesMetadata.RemoteDnsRulesMetadata = withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.getRemoteBlacklistRulesMetadata()
+ DnsRuleType.WHITELIST -> repository.getRemoteWhitelistRulesMetadata()
+ DnsRuleType.IP_BLACKLIST -> repository.getRemoteIpBlacklistRulesMetadata()
+ DnsRuleType.FORWARDING -> repository.getRemoteForwardingRulesMetadata()
+ DnsRuleType.CLOAKING -> repository.getRemoteCloakingRulesMetadata()
+ }
+ }
+
+ override suspend fun getLocalRulesMetadata(
+ type: DnsRuleType
+ ): DnsRulesMetadata.LocalDnsRulesMetadata = withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.getLocalBlacklistRulesMetadata()
+ DnsRuleType.WHITELIST -> repository.getLocalWhitelistRulesMetadata()
+ DnsRuleType.IP_BLACKLIST -> repository.getLocalIpBlacklistRulesMetadata()
+ DnsRuleType.FORWARDING -> repository.getLocalForwardingRulesMetadata()
+ DnsRuleType.CLOAKING -> repository.getLocalCloakingRulesMetadata()
+ }
+ }
+
+ override suspend fun clearRemoteRules(type: DnsRuleType) = withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.clearRemoteBlacklistRules()
+ DnsRuleType.WHITELIST -> repository.clearRemoteWhitelistRules()
+ DnsRuleType.IP_BLACKLIST -> repository.clearRemoteIpBlacklistRules()
+ DnsRuleType.FORWARDING -> repository.clearRemoteForwardingRules()
+ DnsRuleType.CLOAKING -> repository.clearRemoteCloakingRules()
+ }
+ }
+
+ override suspend fun clearLocalRules(type: DnsRuleType) = withContext(dispatcherIo) {
+ when (type) {
+ DnsRuleType.BLACKLIST -> repository.clearLocalBlacklistRules()
+ DnsRuleType.WHITELIST -> repository.clearLocalWhitelistRules()
+ DnsRuleType.IP_BLACKLIST -> repository.clearLocalIpBlacklistRules()
+ DnsRuleType.FORWARDING -> repository.clearLocalForwardingRules()
+ DnsRuleType.CLOAKING -> repository.clearLocalCloakingRules()
+ }
+ }
+
+ override suspend fun isExternalStorageAllowsDirectAccess(): Boolean =
+ runInterruptible(dispatcherIo) {
+ isLogsDirAccessible()
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt
similarity index 59%
rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java
rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt
index 28ecb76cb..cff217bf3 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/Rules.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesMetadata.kt
@@ -17,20 +17,29 @@
Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
*/
-package pan.alexander.tordnscrypt.settings.show_rules;
+package pan.alexander.tordnscrypt.domain.dns_rules
-class Rules {
+import java.util.Date
- String text;
- boolean active;
- boolean locked;
- boolean subscription;
+sealed class DnsRulesMetadata {
+ data class RemoteDnsRulesMetadata(
+ val name: String,
+ val url: String,
+ val date: Date,
+ val count: Int,
+ val size: Long
+ ) : DnsRulesMetadata()
+
+ data class LocalDnsRulesMetadata(
+ val name: String,
+ val date: Date,
+ val count: Int,
+ val size: Long
+ ) : DnsRulesMetadata()
+
+ data class MixedDnsRulesMetadata(
+ val count: Int
+ )
- Rules(String text, boolean active, boolean locked, boolean subscription) {
- this.text = text;
- this.active = active;
- this.locked = locked;
- this.subscription = subscription;
- }
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt
new file mode 100644
index 000000000..b4396add5
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/domain/dns_rules/DnsRulesRepository.kt
@@ -0,0 +1,70 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.domain.dns_rules
+
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+
+interface DnsRulesRepository {
+
+ suspend fun getMixedBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleBlacklistRules(): List
+ fun saveSingleBlacklistRules(rules: List)
+ suspend fun getRemoteBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata
+ fun clearRemoteBlacklistRules()
+ suspend fun getLocalBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata
+ fun clearLocalBlacklistRules()
+
+ suspend fun getMixedWhitelistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleWhitelistRules(): List
+ fun saveSingleWhitelistRules(rules: List)
+ suspend fun getRemoteWhitelistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata
+ fun clearRemoteWhitelistRules()
+ suspend fun getLocalWhitelistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata
+ fun clearLocalWhitelistRules()
+
+ suspend fun getMixedIpBlacklistRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleIpBlacklistRules(): List
+ fun saveSingleIpBlacklistRules(rules: List)
+ suspend fun getRemoteIpBlacklistRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata
+ fun clearRemoteIpBlacklistRules()
+ suspend fun getLocalIpBlacklistRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata
+ fun clearLocalIpBlacklistRules()
+
+ suspend fun getMixedForwardingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleForwardingRules(): List
+ fun saveSingleForwardingRules(rules: List)
+ suspend fun getRemoteForwardingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata
+ fun clearRemoteForwardingRules()
+ suspend fun getLocalForwardingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata
+ fun clearLocalForwardingRules()
+
+ suspend fun getMixedCloakingRulesMetadata(): DnsRulesMetadata.MixedDnsRulesMetadata
+ suspend fun getSingleCloakingRules(): List
+ fun saveSingleCloakingRules(rules: List)
+ suspend fun getRemoteCloakingRulesMetadata(): DnsRulesMetadata.RemoteDnsRulesMetadata
+ fun clearRemoteCloakingRules()
+ suspend fun getLocalCloakingRulesMetadata(): DnsRulesMetadata.LocalDnsRulesMetadata
+ fun clearLocalCloakingRules()
+
+ companion object {
+ const val LOCAL_RULES_DEFAULT_HEADER = "local-rules.txt"
+ const val REMOTE_RULES_DEFAULT_HEADER = "remote-rules.txt"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt
index bc6729761..5d462e7ae 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesFirewall.kt
@@ -20,6 +20,7 @@
package pan.alexander.tordnscrypt.iptables
import android.content.Context
+import android.os.Build
import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
import pan.alexander.tordnscrypt.iptables.IptablesConstants.*
import pan.alexander.tordnscrypt.modules.ModulesStatus
@@ -35,12 +36,14 @@ import pan.alexander.tordnscrypt.settings.tor_apps.ApplicationData.Companion.SPE
import pan.alexander.tordnscrypt.utils.Constants.*
import pan.alexander.tordnscrypt.utils.Utils
import pan.alexander.tordnscrypt.utils.apps.InstalledApplicationsManager
+import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isCellularActive
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isEthernetActive
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isRoaming
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isVpnActive
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.isWifiActive
import pan.alexander.tordnscrypt.utils.connectivitycheck.ConnectivityCheckManager
+import pan.alexander.tordnscrypt.utils.enums.ModuleState
import pan.alexander.tordnscrypt.utils.enums.OperationMode
import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.*
import pan.alexander.tordnscrypt.vpn.VpnUtils
@@ -66,10 +69,34 @@ class IptablesFirewall @Inject constructor(
val uidAllowed by lazy { hashSetOf() }
val uidSpecialAllowed by lazy { hashSetOf() }
val uidLanAllowed by lazy { hashSetOf() }
+ private val uidUnderlyingVpnAllowed by lazy { hashSetOf() }
fun getFirewallRules(tetheringActive: Boolean): List {
- prepareUidAllowed()
+ val vpnActive = isVpnActive(context)
+
+ prepareUidAllowed(vpnActive)
+
+ if (modulesStatus.mode == OperationMode.ROOT_MODE) {
+ modulesStatus.setFirewallState(ModuleState.RUNNING, preferences)
+ }
+
+ var activeInterface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ NetworkChecker.getCurrentActiveInterface(context)
+ } else {
+ ""
+ }
+ val underlyingVpnInterface = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && vpnActive && activeInterface.isNotEmpty()
+ ) {
+ NetworkChecker.getUnderlyingVpnActiveInterface(context).also {
+ if (it.isEmpty()) {
+ activeInterface = ""
+ }
+ }
+ } else {
+ ""
+ }
val iptables = getIptables()
@@ -84,8 +111,14 @@ class IptablesFirewall @Inject constructor(
).plus(
getLanRules(iptables)
).plus(
- getAppRulesByConnectionType(iptables)
- ).plus(
+ getAppRulesByConnectionType(iptables, activeInterface)
+ ).let {
+ if (underlyingVpnInterface.isNotEmpty() && underlyingVpnInterface != activeInterface) {
+ it.plus(getVpnUnderlyingAppRulesByConnectionType(iptables, underlyingVpnInterface))
+ } else {
+ it
+ }
+ }.plus(
getSpecialRules(iptables)
).plus(
"$iptables -A $FILTER_OUTPUT_FIREWALL -j REJECT"
@@ -104,6 +137,10 @@ class IptablesFirewall @Inject constructor(
fun getClearFirewallRules(): List {
+ if (modulesStatus.mode == OperationMode.ROOT_MODE) {
+ modulesStatus.setFirewallState(ModuleState.STOPPED, preferences)
+ }
+
val iptables = getIptables()
return arrayListOf(
@@ -122,7 +159,6 @@ class IptablesFirewall @Inject constructor(
).plus(
VpnUtils.nonTorList
.asSequence()
- .filter { it != "127.0.0.0/8" } //exclude localhost
.filter { it != META_ADDRESS } //exclude meta address
.map {
"$iptables -A $FILTER_OUTPUT_FIREWALL -d $it -j $FILTER_FIREWALL_LAN"
@@ -146,23 +182,61 @@ class IptablesFirewall @Inject constructor(
range.first() == SPECIAL_UID_KERNEL -> "$iptables -A $FILTER_FIREWALL_LAN -m owner ! --uid-owner 0:999999999 -j MARK --set-mark $FIREWALL_RETURN_MARK 2> /dev/null || true"
else -> ""
}
+
range.size > 1 ->
when {
range.first() >= 0 -> "$iptables -A $FILTER_FIREWALL_LAN -m owner --uid-owner ${range.first()}:${range.last()} -j MARK --set-mark $FIREWALL_RETURN_MARK 2> /dev/null || true"
else -> ""
}
+
else -> ""
}
}
}
- private fun getAppRulesByConnectionType(iptables: String): List = with(IptablesUtils) {
- uidAllowed.groupToRanges().map {
+ private fun getAppRulesByConnectionType(
+ iptables: String,
+ activeInterface: String
+ ): List = with(IptablesUtils) {
+ if (activeInterface.isNotEmpty()) {
+ uidAllowed.groupToRanges().map {
+ when {
+ it.size == 1 ->
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -o $activeInterface -j RETURN"
+
+ it.size > 1 ->
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -o $activeInterface -j RETURN"
+
+ else -> ""
+ }
+ }
+ } else {
+ uidAllowed.groupToRanges().map {
+ when {
+ it.size == 1 ->
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -j RETURN"
+
+ it.size > 1 ->
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -j RETURN"
+
+ else -> ""
+ }
+ }
+ }
+ }
+
+ private fun getVpnUnderlyingAppRulesByConnectionType(
+ iptables: String,
+ underlyingInterface: String
+ ): List = with(IptablesUtils) {
+ uidUnderlyingVpnAllowed.groupToRanges().map {
when {
it.size == 1 ->
- "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -j RETURN"
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()} -o $underlyingInterface -j RETURN"
+
it.size > 1 ->
- "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -j RETURN"
+ "$iptables -A $FILTER_OUTPUT_FIREWALL -m owner --uid-owner ${it.first()}:${it.last()} -o $underlyingInterface -j RETURN"
+
else -> ""
}
}
@@ -181,12 +255,14 @@ class IptablesFirewall @Inject constructor(
"$iptables -A $FILTER_OUTPUT_FIREWALL -m owner ! --uid-owner 0:999999999 -j RETURN"
)
}
+
SPECIAL_UID_NTP -> {
arrayListOf(
"$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --sport $SPECIAL_PORT_NTP -m owner --uid-owner 1000 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true",
"$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --dport $SPECIAL_PORT_NTP -m owner --uid-owner 1000 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true",
)
}
+
SPECIAL_UID_AGPS -> {
arrayListOf(
"$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p tcp --dport $SPECIAL_PORT_AGPS1 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true",
@@ -195,11 +271,13 @@ class IptablesFirewall @Inject constructor(
"$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -p udp --dport $SPECIAL_PORT_AGPS2 -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true"
)
}
+
SPECIAL_UID_CONNECTIVITY_CHECK -> {
connectivityCheckManager.get().getConnectivityCheckIps().map { ip ->
"$iptables -t mangle -A $MANGLE_FIREWALL_ALLOW -d $ip -j CONNMARK --set-mark $FIREWALL_RETURN_MARK || true"
}
}
+
else -> emptyList()
}
}
@@ -222,10 +300,13 @@ class IptablesFirewall @Inject constructor(
emptyList()
}
- fun prepareUidAllowed() {
+ fun prepareUidAllowed(vpnActive: Boolean) {
clearAllowedUids()
- fillAllowedAndSpecialUids(getUidsAllowed())
+ fillAllowedAndSpecialUids(getUidsAllowed(vpnActive = vpnActive, skipVpn = false))
fillLanAllowed()
+ if (vpnActive) {
+ fillAllowedUnderlyingVpnUid(getUidsAllowed(vpnActive = true, skipVpn = true))
+ }
}
private fun fillAllowedAndSpecialUids(listAllowed: List) =
@@ -237,6 +318,13 @@ class IptablesFirewall @Inject constructor(
}
}
+ private fun fillAllowedUnderlyingVpnUid(listAllowed: List) =
+ listAllowed.forEach {
+ if (it?.matches(positiveNumberRegex) == true && it.toLong() <= Int.MAX_VALUE) {
+ uidUnderlyingVpnAllowed.add(it.toInt())
+ }
+ }
+
private fun fillLanAllowed() {
preferences.getStringSetPreference(APPS_ALLOW_LAN_PREF).forEach {
if (it.matches(numberRegex) && it.toLong() <= Int.MAX_VALUE) {
@@ -249,9 +337,10 @@ class IptablesFirewall @Inject constructor(
uidAllowed.clear()
uidSpecialAllowed.clear()
uidLanAllowed.clear()
+ uidUnderlyingVpnAllowed.clear()
}
- private fun getUidsAllowed(): List {
+ private fun getUidsAllowed(vpnActive: Boolean, skipVpn: Boolean): List {
val firewallEnabled = preferences.getBoolPreference(FIREWALL_ENABLED)
if (!firewallEnabled || modulesStatus.mode != OperationMode.ROOT_MODE) {
@@ -266,18 +355,24 @@ class IptablesFirewall @Inject constructor(
val listAllowed = arrayListOf()
when {
- isVpnActive(context) && !ttlFix ->
+ vpnActive && !ttlFix && !skipVpn ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_VPN))
+
isWifiActive(context) || isEthernetActive(context) ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_WIFI_PREF))
+
isRoaming(context) ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_ROAMING))
+
isCellularActive(context) ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_GSM_PREF))
+
isWifiActive(context, true) ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_WIFI_PREF))
+
isCellularActive(context, true) ->
listAllowed.addAll(preferences.getStringSetPreference(APPS_ALLOW_GSM_PREF))
+
else -> listAllowed.apply {
add(SPECIAL_UID_KERNEL.toString())
add(ROOT_DEFAULT_UID.toString())
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java
index 04625fc8e..510d7213c 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/IptablesRules.java
@@ -24,7 +24,12 @@
import pan.alexander.tordnscrypt.utils.enums.ModuleState;
public interface IptablesRules {
- List configureIptables(ModuleState dnsCryptState, ModuleState torState, ModuleState itpdState);
+ List configureIptables(
+ ModuleState dnsCryptState,
+ ModuleState torState,
+ ModuleState itpdState,
+ ModuleState firewallState
+ );
List fastUpdate();
void refreshFixTTLRules();
List clearAll();
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java
index 644eb0e7e..d4da7bc2a 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/iptables/ModulesIptablesRules.java
@@ -66,6 +66,7 @@
import static pan.alexander.tordnscrypt.utils.Constants.NFLOG_GROUP;
import static pan.alexander.tordnscrypt.utils.Constants.NFLOG_PREFIX;
import static pan.alexander.tordnscrypt.utils.Constants.QUAD_DNS_41;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALL_THROUGH_TOR;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.APPS_DIRECT_UDP;
@@ -126,7 +127,12 @@ public ModulesIptablesRules(Context context) {
}
@Override
- public List configureIptables(ModuleState dnsCryptState, ModuleState torState, ModuleState itpdState) {
+ public List configureIptables(
+ ModuleState dnsCryptState,
+ ModuleState torState,
+ ModuleState itpdState,
+ ModuleState firewallState
+ ) {
iptables = pathVars.getIptablesPath();
ip6tables = pathVars.getIp6tablesPath();
@@ -377,12 +383,18 @@ public List configureIptables(ModuleState dnsCryptState, ModuleState tor
commands = getBlockingRules(appUID, blockHOTSPOT, unblockHOTSPOT);
- } else if (killSwitch && dnsCryptState != RUNNING && torState != RUNNING && itpdState != RUNNING) {
+ } else if (killSwitch
+ && dnsCryptState != RUNNING && torState != RUNNING && itpdState != RUNNING
+ && firewallState != RUNNING && firewallState != STARTING) {
showKillSwitchNotification();
commands = getBlockingRules(appUID, blockHOTSPOT, unblockHOTSPOT);
+ if (modulesStatus.getMode() == ROOT_MODE) {
+ modulesStatus.setFirewallState(ModuleState.STOPPED, preferences);
+ }
+
} else if (dnsCryptState == RUNNING && torState == RUNNING) {
cancelKillSwitchNotificationIfNeeded();
@@ -598,6 +610,56 @@ public List configureIptables(ModuleState dnsCryptState, ModuleState tor
blockHOTSPOT
));
+ List commandsTether = tethering.activateTethering(false);
+ if (!commandsTether.isEmpty()) {
+ commands.addAll(commandsTether);
+ }
+ if (firewallEnabled) {
+ commands.addAll(firewall.getFirewallRules(tethering.isTetheringActive()));
+ } else {
+ commands.addAll(firewall.getClearFirewallRules());
+ }
+ commands.add(iptables + "-D OUTPUT -j " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null || true");
+ } else if (dnsCryptState == STOPPED && torState == STOPPED
+ && (firewallState == STARTING || firewallState == RUNNING)) {
+
+ cancelKillSwitchNotificationIfNeeded();
+
+ commands = new ArrayList<>(Arrays.asList(
+ iptables + "-F " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null",
+ iptables + "-D OUTPUT -j " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null || true",
+ iptables + "-N " + FILTER_OUTPUT_BLOCKING + " 2> /dev/null",
+ iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -m state --state ESTABLISHED,RELATED -j RETURN",
+ iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -m owner --uid-owner " + appUID + " -j RETURN",
+ criticalUidsAllowed,
+ iptables + "-A " + FILTER_OUTPUT_BLOCKING + " -j DROP",
+ iptables + "-I OUTPUT -j " + FILTER_OUTPUT_BLOCKING,
+ ip6tables + "-D OUTPUT -j DROP 2> /dev/null || true",
+ ip6tables + "-D OUTPUT -m owner --uid-owner " + appUID + " -j ACCEPT 2> /dev/null || true",
+ ip6tables + "-I OUTPUT -j DROP",
+ ip6tables + "-I OUTPUT -m owner --uid-owner " + appUID + " -j ACCEPT",
+ iptables + "-t nat -F " + NAT_OUTPUT_CORE + " 2> /dev/null",
+ iptables + "-t nat -D OUTPUT -j " + NAT_OUTPUT_CORE + " 2> /dev/null || true",
+ iptables + "-F " + FILTER_OUTPUT_CORE + " 2> /dev/null",
+ iptables + "-D OUTPUT -j " + FILTER_OUTPUT_CORE + " 2> /dev/null || true",
+ busybox + "sleep 1 || true",
+ iptables + "-t nat -N " + NAT_OUTPUT_CORE + " 2> /dev/null",
+ iptables + "-t nat -I OUTPUT -j " + NAT_OUTPUT_CORE,
+ iptables + "-t nat -A " + NAT_OUTPUT_CORE + " -p all -d 127.0.0.1/32 -j RETURN",
+ blockHttpRuleNatTCP,
+ blockHttpRuleNatUDP,
+ iptables + "-N " + FILTER_OUTPUT_CORE + " 2> /dev/null",
+ nflogDns,
+ iptables + "-A " + FILTER_OUTPUT_CORE + " -p udp -m udp --dport 53 -j ACCEPT",
+ iptables + "-A " + FILTER_OUTPUT_CORE + " -p tcp -m tcp --dport 53 -j ACCEPT",
+ blockRejectAddressFilter,
+ iptables + "-A " + FILTER_OUTPUT_CORE + " -m state --state ESTABLISHED,RELATED -j RETURN",
+ iptables + "-I OUTPUT -j " + FILTER_OUTPUT_CORE,
+ nflogPackets,
+ unblockHOTSPOT,
+ blockHOTSPOT
+ ));
+
List commandsTether = tethering.activateTethering(false);
if (!commandsTether.isEmpty()) {
commands.addAll(commandsTether);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java
index 52030e767..732576781 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/itpd_fragment/ITPDRunFragment.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -44,14 +45,23 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.MainActivity;
import pan.alexander.tordnscrypt.R;
import pan.alexander.tordnscrypt.TopFragment;
+import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository;
+import pan.alexander.tordnscrypt.modules.ModulesAux;
+import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.utils.root.RootExecService;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static pan.alexander.tordnscrypt.TopFragment.ITPDVersion;
import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import com.google.android.material.divider.MaterialDivider;
@@ -76,7 +86,7 @@ public ITPDRunFragment() {
@SuppressLint("SetTextI18n")
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view;
@@ -213,10 +223,32 @@ public void onDestroyView() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnITPDStart) {
+ manageFirewall();
presenter.startButtonOnClick();
}
}
+ private void manageFirewall() {
+ ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if ((modulesStatus.getDnsCryptState() == STOPPED
+ || modulesStatus.getDnsCryptState() == STOPPING
+ || modulesStatus.getDnsCryptState() == FAULT)
+ && (modulesStatus.getTorState() == STOPPED
+ || modulesStatus.getTorState() == STOPPING
+ || modulesStatus.getTorState() == FAULT)) {
+ PreferenceRepository preferenceRepository = App.getInstance()
+ .getDaggerComponent().getPreferenceRepository().get();
+ if (modulesStatus.getFirewallState() != STOPPED
+ && modulesStatus.getItpdState() == RUNNING) {
+ modulesStatus.setFirewallState(STOPPING, preferenceRepository);
+ } else if (modulesStatus.getFirewallState() != RUNNING
+ && modulesStatus.getItpdState() == STOPPED) {
+ modulesStatus.setFirewallState(STARTING, preferenceRepository);
+ ModulesAux.makeModulesStateExtraLoop(requireContext());
+ }
+ }
+ }
+
@Override
public void setITPDStatus(int resourceText, int resourceColor) {
tvITPDStatus.setText(resourceText);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java
index 5c9cea6f9..459097787 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/main_fragment/MainFragment.java
@@ -67,12 +67,15 @@
import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentPresenter;
import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentReceiver;
import pan.alexander.tordnscrypt.dnscrypt_fragment.DNSCryptFragmentView;
+import pan.alexander.tordnscrypt.modules.ModulesAux;
import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.tor_fragment.TorFragmentPresenter;
import pan.alexander.tordnscrypt.tor_fragment.TorFragmentReceiver;
import pan.alexander.tordnscrypt.tor_fragment.TorFragmentView;
import pan.alexander.tordnscrypt.utils.Utils;
+import pan.alexander.tordnscrypt.utils.enums.OperationMode;
import pan.alexander.tordnscrypt.utils.root.RootExecService;
+import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -81,10 +84,14 @@
import static pan.alexander.tordnscrypt.TopFragment.ITPDVersion;
import static pan.alexander.tordnscrypt.TopFragment.TOP_BROADCAST;
import static pan.alexander.tordnscrypt.TopFragment.TorVersion;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.UNDEFINED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED;
import com.google.android.material.divider.MaterialDivider;
@@ -336,22 +343,50 @@ public void onClick(View v) {
if (v.getId() == R.id.btnStartMainFragment) {
+ boolean runDnsCrypt = chbProtectDnsMainFragment.isChecked();
+ boolean runTor = chbHideIpMainFragment.isChecked();
+ boolean runITPD = chbAccessITPMainFragment.isChecked();
+
if (modulesStatus.getDnsCryptState() == STOPPED
&& modulesStatus.getTorState() == STOPPED
- && modulesStatus.getItpdState() == STOPPED) {
+ && modulesStatus.getItpdState() == STOPPED
+ && modulesStatus.getFirewallState() != RUNNING) {
+
+ if (modulesStatus.getFirewallState() == STOPPED
+ && (modulesStatus.getMode() == OperationMode.VPN_MODE
+ || modulesStatus.getMode() == OperationMode.ROOT_MODE)
+ && isChildLockDisabled()) {
+ modulesStatus.setFirewallState(STARTING, preferenceRepository.get());
+ if (!runDnsCrypt && !runTor && !runITPD && isFirewallEnabled()) {
+ ModulesAux.makeModulesStateExtraLoop(context);
+ ServiceVPNHelper.prepareVPNServiceIfRequired(getFragmentActivity(), modulesStatus);
+ }
+ } else if (isChildLockDisabled()) {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
- if (chbProtectDnsMainFragment.isChecked()) {
+ if (runDnsCrypt) {
dnsCryptFragmentPresenter.startButtonOnClick();
}
- if (chbHideIpMainFragment.isChecked()) {
+ if (runTor) {
torFragmentPresenter.startButtonOnClick();
}
- if (chbAccessITPMainFragment.isChecked()) {
+ if (runITPD) {
itpdFragmentPresenter.startButtonOnClick();
}
} else {
+
+ if (modulesStatus.getFirewallState() != STOPPED && isChildLockDisabled()) {
+ modulesStatus.setFirewallState(STOPPING, preferenceRepository.get());
+ if (!runDnsCrypt && !runTor && !runITPD && isFirewallEnabled()) {
+ ModulesAux.makeModulesStateExtraLoop(context);
+ }
+ } else if (isChildLockDisabled()) {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
+
if (modulesStatus.getDnsCryptState() != STOPPED) {
dnsCryptFragmentPresenter.startButtonOnClick();
}
@@ -363,10 +398,25 @@ public void onClick(View v) {
if (modulesStatus.getItpdState() != STOPPED) {
itpdFragmentPresenter.startButtonOnClick();
}
+
}
}
}
+ private boolean isFirewallEnabled() {
+ return preferenceRepository.get().getBoolPreference(FIREWALL_ENABLED)
+ && preferenceRepository.get().getBoolPreference(FIREWALL_WAS_STARTED);
+ }
+
+ private boolean isChildLockDisabled() {
+ boolean childLock = false;
+ Activity activity = getFragmentActivity();
+ if (activity instanceof MainActivity) {
+ childLock = ((MainActivity) activity).childLockActive;
+ }
+ return !childLock;
+ }
+
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -389,7 +439,8 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (modulesStatus.getDnsCryptState() != STOPPED
|| modulesStatus.getTorState() != STOPPED
- || modulesStatus.getItpdState() != STOPPED) {
+ || modulesStatus.getItpdState() != STOPPED
+ || modulesStatus.getFirewallState() != STOPPED) {
int id = buttonView.getId();
if (id == R.id.chbProtectDnsMainFragment) {
@@ -425,10 +476,10 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
}
}
- private void refreshStartButtonText() {
+ public void refreshStartButtonText() {
Context context = getActivity();
- if (context == null || orientationLandscape) {
+ if (context == null || btnStartMainFragment == null || orientationLandscape) {
return;
}
@@ -436,7 +487,8 @@ private void refreshStartButtonText() {
if ((modulesStatus.getDnsCryptState() == STOPPED || modulesStatus.getDnsCryptState() == UNDEFINED)
&& (modulesStatus.getTorState() == STOPPED || modulesStatus.getTorState() == UNDEFINED)
- && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == UNDEFINED)) {
+ && (modulesStatus.getItpdState() == STOPPED || modulesStatus.getItpdState() == UNDEFINED)
+ && (modulesStatus.getFirewallState() == STOPPED || modulesStatus.getFirewallState() == STOPPING)) {
mainStartButtonDrawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.button_main_selector, context.getTheme());
@@ -466,6 +518,9 @@ private void refreshStartButtonText() {
}
private void setChbProtectDnsMainFragment(boolean checked) {
+ if (chbProtectDnsMainFragment == null) {
+ return;
+ }
if (!chbProtectDnsMainFragment.isChecked() && checked) {
chbProtectDnsMainFragment.setChecked(true);
} else if (chbProtectDnsMainFragment.isChecked() && !checked) {
@@ -602,6 +657,9 @@ public void scrollDNSCryptLogViewToBottom() {
private void setChbHideIpMainFragment(boolean checked) {
+ if (chbHideIpMainFragment == null) {
+ return;
+ }
if (!chbHideIpMainFragment.isChecked() && checked) {
chbHideIpMainFragment.setChecked(true);
} else if (chbHideIpMainFragment.isChecked() && !checked) {
@@ -862,6 +920,9 @@ public void scrollITPDLogViewToBottom() {
}
private void setChbAccessITPMainFragment(boolean checked) {
+ if (chbAccessITPMainFragment == null) {
+ return;
+ }
if (!chbAccessITPMainFragment.isChecked() && checked) {
chbAccessITPMainFragment.setChecked(true);
} else if (chbAccessITPMainFragment.isChecked() && !checked) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java
index 537cc84a8..7d495e2f5 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesAux.java
@@ -25,12 +25,15 @@
import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository;
import pan.alexander.tordnscrypt.settings.PathVars;
+import pan.alexander.tordnscrypt.utils.enums.ModuleState;
import pan.alexander.tordnscrypt.utils.enums.OperationMode;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.PROXY_MODE;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.ROOT_MODE;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.UNDEFINED;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.VPN_MODE;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OPERATION_MODE;
public class ModulesAux {
@@ -38,6 +41,7 @@ public class ModulesAux {
private static final String DNSCRYPT_RUNNING_PREF = "DNSCrypt Running";
private static final String TOR_RUNNING_PREF = "Tor Running";
private static final String ITPD_RUNNING_PREF = "I2PD Running";
+ private static final String FIREWALL_RUNNING_PREF = "Firewall Running";
public static void switchModes(boolean rootIsAvailable, boolean runModulesWithRoot, OperationMode operationMode) {
ModulesStatus modulesStatus = ModulesStatus.getInstance();
@@ -105,6 +109,18 @@ public static void saveITPDStateRunning(boolean running) {
//}
}
+ public static boolean isFirewallSavedStateRunning() {
+ PreferenceRepository preferences = App.getInstance().getDaggerComponent().getPreferenceRepository().get();
+ return preferences.getBoolPreference(FIREWALL_RUNNING_PREF)
+ && preferences.getBoolPreference(FIREWALL_ENABLED)
+ && preferences.getBoolPreference(FIREWALL_WAS_STARTED);
+ }
+
+ public static void saveFirewallStateRunning(boolean running) {
+ PreferenceRepository preferences = App.getInstance().getDaggerComponent().getPreferenceRepository().get();
+ preferences.setBoolPreference(FIREWALL_RUNNING_PREF, running);
+ }
+
public static void stopModulesIfRunning(Context context) {
boolean dnsCryptRunning = isDnsCryptSavedStateRunning();
boolean torRunning = isTorSavedStateRunning();
@@ -121,6 +137,13 @@ public static void stopModulesIfRunning(Context context) {
if (itpdRunning) {
ModulesKiller.stopITPD(context);
}
+
+ ModulesStatus.getInstance().setFirewallState(
+ ModuleState.STOPPED,
+ App.getInstance().getDaggerComponent().getPreferenceRepository().get()
+ );
+ saveFirewallStateRunning(false);
+ speedupModulesStateLoopTimer(context);
}
public static void stopModulesService(Context context) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java
index 66fc499f3..e6c5676ec 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesReceiver.java
@@ -35,6 +35,7 @@
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_DNS64_PREFIX;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.GSM_ON_REQUESTED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.IGNORE_SYSTEM_DNS;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.KILL_SWITCH;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REFRESH_RULES;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.VPN_SERVICE_ENABLED;
@@ -446,7 +447,7 @@ public void onAvailable(@NonNull Network network) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && lastNetwork != network.hashCode()) {
PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy(
- context, null
+ context, null, isIgnoreSystemDns()
);
}
@@ -543,7 +544,7 @@ public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkPrope
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy(
- context, linkProperties
+ context, linkProperties, isIgnoreSystemDns()
);
}
}
@@ -746,6 +747,10 @@ private void restartDNSCryptIfRunning() {
}
}
+ private boolean isIgnoreSystemDns() {
+ return defaultPreferences.get().getBoolean(IGNORE_SYSTEM_DNS, false);
+ }
+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void unlistenNetworkChanges() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java
index 25dec7392..faedd0f02 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesService.java
@@ -149,6 +149,7 @@ public class ModulesService extends Service {
private ModulesKiller modulesKiller;
private UsageStatistic usageStatistic;
private ArpScanner arpScanner;
+ private ModulesServiceNotificationManager serviceNotificationManager;
public ModulesService() {
}
@@ -170,13 +171,9 @@ public void onCreate() {
message = usageStatistic.getMessage(System.currentTimeMillis());
}
- ModulesServiceNotificationManager serviceNotificationManager = new ModulesServiceNotificationManager(
- this,
- systemNotificationManager,
- UsageStatistics.getStartTime()
- );
- serviceNotificationManager.createNotificationChannel();
- serviceNotificationManager.sendNotification(title, message);
+ serviceNotificationManager = ModulesServiceNotificationManager.getManager(this);
+ serviceNotificationManager.createNotificationChannel(this);
+ serviceNotificationManager.sendNotification(this, title, message, UsageStatistics.getStartTime());
}
App.getInstance().getDaggerComponent().inject(this);
@@ -215,14 +212,17 @@ public int onStartCommand(Intent intent, int flags, int startId) {
title = usageStatistic.getTitle();
message = usageStatistic.getMessage(System.currentTimeMillis());
}
-
- ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager(
+ if (serviceNotificationManager == null) {
+ serviceNotificationManager = ModulesServiceNotificationManager
+ .getManager(this);
+ }
+ serviceNotificationManager.sendNotification(
this,
- systemNotificationManager,
+ title,
+ message,
UsageStatistics.getStartTime()
);
- notification.sendNotification(title, message);
- usageStatistic.setServiceNotification(notification);
+ usageStatistic.setServiceNotification(serviceNotificationManager);
if (usageStatistic.isStatisticAllowed()) {
usageStatistic.startUpdate();
@@ -870,8 +870,8 @@ void slowdownTimer() {
private void makeExtraLoop() {
if (timerPeriod != TIMER_HIGH_SPEED && checkModulesStateTask != null) {
executor.submit("ModulesService makeExtraLoop", () -> {
- checkModulesStateTask.run();
- return null;
+ checkModulesStateTask.run();
+ return null;
});
}
}
@@ -924,6 +924,8 @@ public void onDestroy() {
usageStatistic.stopUpdate();
}
+ ModulesServiceNotificationManager.stopManager(this);
+
releaseWakelocks();
if (checkModulesStateTask != null && modulesStatus.getMode() == VPN_MODE) {
@@ -1145,11 +1147,13 @@ private void cleanLogFileNoRootMethod(String logFilePath, String text) {
}
private void startArpScanner() {
- try {
- arpScanner = ArpScanner.getArpComponent().get();
- arpScanner.start();
- } catch (Exception e) {
- loge("ModulesService startArpScanner", e);
+ if (arpScanner == null) {
+ try {
+ arpScanner = ArpScanner.getArpComponent().get();
+ arpScanner.start();
+ } catch (Exception e) {
+ loge("ModulesService startArpScanner", e);
+ }
}
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java
index ed51ed117..a562d8cad 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesServiceNotificationManager.java
@@ -19,6 +19,9 @@
package pan.alexander.tordnscrypt.modules;
+import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.content.Context.RECEIVER_NOT_EXPORTED;
+
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -26,7 +29,10 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ServiceInfo;
import android.os.Build;
@@ -34,43 +40,39 @@
import pan.alexander.tordnscrypt.MainActivity;
import pan.alexander.tordnscrypt.R;
+import pan.alexander.tordnscrypt.utils.enums.ModuleState;
import static pan.alexander.tordnscrypt.modules.ModulesService.DEFAULT_NOTIFICATION_ID;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
-public class ModulesServiceNotificationManager {
+public class ModulesServiceNotificationManager extends BroadcastReceiver {
private final static String ANDROID_CHANNEL_ID = "InviZible";
- private final Service service;
- private final NotificationManager notificationManager;
- private final Long startTime;
- private final PendingIntent contentIntent;
- private final int iconResource;
-
- public ModulesServiceNotificationManager(Service service, NotificationManager notificationManager, Long startTime) {
- this.service = service;
- this.notificationManager = notificationManager;
- this.startTime = startTime;
- this.contentIntent = getContentIntent();
- this.iconResource = getIconResource();
+ private final static String STOP_ALL_ACTION = "pan.alexander.tordnscrypt.NOTIFICATION_STOP_ALL_ACTION";
+ private final static int STOP_ALL_ACTION_CODE = 1120;
+ private static volatile ModulesServiceNotificationManager instance;
+
+ private final ModulesStatus modulesStatus = ModulesStatus.getInstance();
+
+ public ModulesServiceNotificationManager() {
}
@SuppressLint("UnspecifiedImmutableFlag")
- private PendingIntent getContentIntent() {
- Intent notificationIntent = new Intent(service, MainActivity.class);
+ private PendingIntent getContentIntent(Context context) {
+ Intent notificationIntent = new Intent(context, MainActivity.class);
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
contentIntent = PendingIntent.getActivity(
- service.getApplicationContext(),
+ context.getApplicationContext(),
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
contentIntent = PendingIntent.getActivity(
- service.getApplicationContext(),
+ context.getApplicationContext(),
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
@@ -79,12 +81,35 @@ private PendingIntent getContentIntent() {
return contentIntent;
}
+ private PendingIntent getStopIntent(Context context) {
+
+ Intent intent = new Intent(context, ModulesServiceNotificationManager.class);
+ intent.setAction(STOP_ALL_ACTION);
+
+ PendingIntent stopIntent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ stopIntent = PendingIntent.getBroadcast(
+ context.getApplicationContext(),
+ STOP_ALL_ACTION_CODE,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ } else {
+ stopIntent = PendingIntent.getBroadcast(
+ context.getApplicationContext(),
+ STOP_ALL_ACTION_CODE,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ return stopIntent;
+ }
+
@TargetApi(Build.VERSION_CODES.O)
- public void createNotificationChannel() {
+ public void createNotificationChannel(Context context) {
NotificationChannel channel = new NotificationChannel(
ANDROID_CHANNEL_ID,
- service.getString(R.string.notification_channel_services),
- NotificationManager.IMPORTANCE_MIN
+ context.getString(R.string.notification_channel_services),
+ NotificationManager.IMPORTANCE_LOW
);
channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
channel.setDescription("");
@@ -92,50 +117,58 @@ public void createNotificationChannel() {
channel.enableVibration(false);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
channel.setShowBadge(false);
- notificationManager.createNotificationChannel(channel);
+ getNotificationManager(context).createNotificationChannel(channel);
}
- private int getIconResource() {
+ private NotificationManager getNotificationManager(Context context) {
+ return (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+ }
+
+ private int getSmallIcon(Context context) {
int iconResource = android.R.drawable.ic_menu_view;
try {
- iconResource = service.getResources().getIdentifier(
+ iconResource = context.getResources().getIdentifier(
"ic_service_notification",
"drawable",
- service.getPackageName()
+ context.getPackageName()
);
if (iconResource == 0) {
iconResource = android.R.drawable.ic_menu_view;
}
} catch (Exception e) {
- loge("ModulesServiceNotificationManager getIconResource", e);
+ loge("ModulesServiceNotificationManager getSmallIcon", e);
}
return iconResource;
}
- public synchronized void sendNotification(String title, String text) {
-
- if (service == null || notificationManager == null) {
- return;
- }
+ public synchronized void sendNotification(Service service, String title, String text, long startTime) {
- notificationManager.cancel(DEFAULT_NOTIFICATION_ID);
+ getNotificationManager(service).cancel(DEFAULT_NOTIFICATION_ID);
NotificationCompat.Builder builder = new NotificationCompat.Builder(service, ANDROID_CHANNEL_ID);
- builder.setContentIntent(contentIntent)
+ builder.setContentIntent(getContentIntent(service))
.setOngoing(true)
- .setSmallIcon(iconResource)
+ .setSmallIcon(getSmallIcon(service))
.setContentTitle(title)
.setContentText(text)
- .setPriority(Notification.PRIORITY_MIN)
+ .setPriority(Notification.PRIORITY_LOW)
.setOnlyAlertOnce(true)
.setSilent(true)
.setChannelId(ANDROID_CHANNEL_ID)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isAnyModuleRunning()) {
+ builder.addAction(
+ R.drawable.ic_close_white,
+ service.getText(R.string.main_fragment_button_stop),
+ getStopIntent(service)
+ );
+ }
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_SERVICE);
}
@@ -159,23 +192,27 @@ public synchronized void sendNotification(String title, String text) {
}
@SuppressLint("UnspecifiedImmutableFlag")
- public void updateNotification(String title, String text) {
- if (service == null || notificationManager == null) {
- return;
- }
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(service, ANDROID_CHANNEL_ID);
- builder.setContentIntent(contentIntent)
+ public void updateNotification(Context context, String title, String text, long startTime) {
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ANDROID_CHANNEL_ID);
+ builder.setContentIntent(getContentIntent(context))
.setOngoing(true)
- .setSmallIcon(iconResource)
+ .setSmallIcon(getSmallIcon(context))
.setContentTitle(title)
.setContentText(text)
- .setPriority(Notification.PRIORITY_MIN)
+ .setPriority(Notification.PRIORITY_LOW)
.setOnlyAlertOnce(true)
.setSilent(true)
.setChannelId(ANDROID_CHANNEL_ID)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isAnyModuleRunning()) {
+ builder.addAction(
+ R.drawable.ic_close_white,
+ context.getText(R.string.main_fragment_button_stop),
+ getStopIntent(context)
+ );
+ }
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_SERVICE);
}
@@ -187,6 +224,69 @@ public void updateNotification(String title, String text) {
Notification notification = builder.build();
- notificationManager.notify(DEFAULT_NOTIFICATION_ID, notification);
+ getNotificationManager(context).notify(DEFAULT_NOTIFICATION_ID, notification);
+ }
+
+ private boolean isAnyModuleRunning() {
+ return modulesStatus.getDnsCryptState() == ModuleState.RUNNING
+ || modulesStatus.getTorState() == ModuleState.RUNNING
+ || modulesStatus.getItpdState() == ModuleState.RUNNING
+ || modulesStatus.getFirewallState() == ModuleState.RUNNING;
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ public static ModulesServiceNotificationManager getManager(Context context) {
+ if (instance == null) {
+ synchronized (ModulesServiceNotificationManager.class) {
+ if (instance == null) {
+ instance = new ModulesServiceNotificationManager();
+ IntentFilter filter = new IntentFilter(STOP_ALL_ACTION);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.getApplicationContext().registerReceiver(
+ instance,
+ filter,
+ RECEIVER_NOT_EXPORTED
+ );
+ } else {
+ context.getApplicationContext().registerReceiver(
+ instance,
+ filter
+ );
+ }
+ } catch (Exception e) {
+ loge("ModulesServiceNotificationManager getNotificationManager", e);
+ }
+ return instance;
+ }
+ }
+ }
+ return instance;
+ }
+
+ public static void stopManager(Context context) {
+ if (instance != null) {
+ synchronized (ModulesServiceNotificationManager.class) {
+ if (instance != null) {
+ try {
+ context.getApplicationContext().unregisterReceiver(instance);
+ } catch (Exception e) {
+ loge("ModulesServiceNotificationManager stopNotificationManager", e);
+ }
+ instance = null;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (context != null && intent != null && STOP_ALL_ACTION.equals(intent.getAction())) {
+ stopServices(context);
+ }
+ }
+
+ private void stopServices(Context context) {
+ ModulesAux.stopModulesIfRunning(context);
}
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java
index 5d419581f..8e717426e 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStateLoop.java
@@ -55,6 +55,8 @@
import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper;
import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logw;
@@ -126,6 +128,7 @@ public class ModulesStateLoop implements Runnable,
private ModuleState savedDNSCryptState = UNDEFINED;
private ModuleState savedTorState = UNDEFINED;
private ModuleState savedItpdState = UNDEFINED;
+ private ModuleState savedFirewallState = UNDEFINED;
private final SharedPreferences sharedPreferences;
@@ -184,6 +187,7 @@ public synchronized void run() {
modulesStatus.getDnsCryptState(),
modulesStatus.getTorState(),
modulesStatus.getItpdState(),
+ modulesStatus.getFirewallState(),
operationMode,
rootIsAvailable,
useModulesWithRoot
@@ -204,6 +208,7 @@ public synchronized void run() {
logi("ModulesStateLoop stopCounter is zero. Stop service.");
modulesStatus.setContextUIDUpdateRequested(false);
safeStopModulesService();
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
}
slowDownModulesStateTimerIfRequired();
@@ -263,9 +268,14 @@ private void updateFixTTLRules() {
}
}
- private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState,
- ModuleState itpdState, OperationMode operationMode,
- boolean rootIsAvailable, boolean useModulesWithRoot) {
+ private void updateIptablesRules(
+ ModuleState dnsCryptState,
+ ModuleState torState,
+ ModuleState itpdState,
+ ModuleState firewallState,
+ OperationMode operationMode,
+ boolean rootIsAvailable,
+ boolean useModulesWithRoot) {
/* For testing purposes
logi(String.format("DNSCrypt is %s Tor is %s I2P is %s\n" +
"Operation mode %s Use modules with Root %s " +
@@ -277,10 +287,11 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState
if (dnsCryptState != savedDNSCryptState
|| torState != savedTorState
|| itpdState != savedItpdState
+ || firewallState != savedFirewallState
|| modulesStatus.isIptablesRulesUpdateRequested()) {
- logi(String.format("DNSCrypt is %s Tor is %s I2P is %s\n" +
+ logi(String.format("DNSCrypt is %s Tor is %s I2P is %s Firewall is %s\n" +
"Operation mode %s Use modules with Root %s",
- dnsCryptState, torState, itpdState,
+ dnsCryptState, torState, itpdState, firewallState,
operationMode, useModulesWithRoot));
if (dnsCryptState == RESTARTING) {
@@ -372,6 +383,14 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState
}
}
+ if (savedFirewallState != firewallState) {
+ saveFirewallState(firewallState);
+ if (firewallState == STARTING || firewallState == RUNNING) {
+ modulesStatusBroadcaster.get().broadcastFirewallRunning();
+ } else if (firewallState == STOPPED) {
+ modulesStatusBroadcaster.get().broadcastFirewallStopped();
+ }
+ }
if (modulesStatus.isIptablesRulesUpdateRequested()) {
modulesStatus.setIptablesRulesUpdateRequested(false);
@@ -380,7 +399,12 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState
boolean vpnServiceEnabled = sharedPreferences.getBoolean(VPN_SERVICE_ENABLED, false);
if (iptablesRules != null && rootIsAvailable && operationMode == ROOT_MODE) {
- List commands = iptablesRules.configureIptables(dnsCryptState, torState, itpdState);
+ List commands = iptablesRules.configureIptables(
+ dnsCryptState,
+ torState,
+ itpdState,
+ firewallState
+ );
int hashCode = commands.hashCode();
if (hashCode == savedIptablesCommandsHash && !iptablesRules.isLastIptablesCommandsReturnError()) {
@@ -396,7 +420,8 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState
stopCounter = STOP_COUNTER_DELAY;
} else if (operationMode == VPN_MODE) {
- if (dnsCryptState == STOPPED && torState == STOPPED) {
+ if (dnsCryptState == STOPPED && torState == STOPPED
+ && (firewallState == STOPPED || firewallState == STOPPING)) {
ServiceVPNHelper.stop("All modules stopped", modulesService);
} else if (vpnServiceEnabled) {
ServiceVPNHelper.reload("Modules state changed", modulesService);
@@ -453,7 +478,8 @@ private void updateIptablesRules(ModuleState dnsCryptState, ModuleState torState
stopCounter--;
} else if ((dnsCryptState == STOPPED || dnsCryptState == FAULT)
&& (torState == STOPPED || torState == FAULT)
- && (itpdState == STOPPED || itpdState == FAULT)) {
+ && (itpdState == STOPPED || itpdState == FAULT)
+ && (firewallState == STOPPING || firewallState == STOPPED)) {
stopCounter--;
}
@@ -507,7 +533,10 @@ private void startVPNService() {
handler.get().postDelayed(() -> {
if (modulesService != null && modulesStatus != null && sharedPreferences != null
&& !sharedPreferences.getBoolean(VPN_SERVICE_ENABLED, false)
- && (modulesStatus.getDnsCryptState() == RUNNING || modulesStatus.getTorState() == RUNNING)) {
+ && (modulesStatus.getDnsCryptState() == RUNNING
+ || modulesStatus.getTorState() == RUNNING
+ || modulesStatus.getFirewallState() == STARTING
+ || modulesStatus.getFirewallState() == RUNNING)) {
sharedPreferences.edit().putBoolean(VPN_SERVICE_ENABLED, true).apply();
ServiceVPNHelper.start("ModulesStateLoop start VPN service", modulesService);
}
@@ -660,6 +689,15 @@ private void setITPDReady(boolean ready) {
}
}
+ private void saveFirewallState(ModuleState firewallState) {
+ savedFirewallState = firewallState;
+ if (firewallState == RUNNING) {
+ ModulesAux.saveFirewallStateRunning(true);
+ } else if (firewallState == STOPPED) {
+ ModulesAux.saveFirewallStateRunning(false);
+ }
+ }
+
@Override
public boolean isActive() {
return ModulesService.serviceIsRunning;
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java
index a10d68544..8b80f8885 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatus.java
@@ -23,10 +23,14 @@
import androidx.annotation.NonNull;
+import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository;
import pan.alexander.tordnscrypt.utils.enums.ModuleState;
import pan.alexander.tordnscrypt.utils.enums.OperationMode;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.UNDEFINED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_WAS_STARTED;
public final class ModulesStatus {
@@ -34,6 +38,8 @@ public final class ModulesStatus {
private volatile ModuleState torState = UNDEFINED;
private volatile ModuleState itpdState = UNDEFINED;
+ private volatile ModuleState firewallState = STOPPED;
+
private volatile boolean rootAvailable = false;
private volatile boolean useModulesWithRoot;
private volatile boolean requestIptablesUpdate;
@@ -80,6 +86,10 @@ public ModuleState getItpdState() {
return itpdState;
}
+ public ModuleState getFirewallState() {
+ return firewallState;
+ }
+
public void setDnsCryptState(ModuleState dnsCryptState) {
this.dnsCryptState = dnsCryptState;
}
@@ -92,6 +102,17 @@ public void setItpdState(ModuleState itpdState) {
this.itpdState = itpdState;
}
+ public void setFirewallState(ModuleState firewallState, PreferenceRepository preferenceRepository) {
+ boolean firewallEnabled = preferenceRepository.getBoolPreference(FIREWALL_ENABLED)
+ && preferenceRepository.getBoolPreference(FIREWALL_WAS_STARTED);
+ if (firewallEnabled
+ && (mode == OperationMode.VPN_MODE || mode == OperationMode.ROOT_MODE)) {
+ this.firewallState = firewallState;
+ } else {
+ this.firewallState = STOPPED;
+ }
+ }
+
public boolean isUseModulesWithRoot() {
return useModulesWithRoot;
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt
index 3789d2154..c11b6f9fe 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/ModulesStatusBroadcaster.kt
@@ -22,6 +22,7 @@ package pan.alexander.tordnscrypt.modules
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
import pan.alexander.tordnscrypt.di.SharedPreferencesModule.Companion.DEFAULT_PREFERENCES_NAME
import pan.alexander.tordnscrypt.settings.PathVars
import pan.alexander.tordnscrypt.utils.logger.Logger.logi
@@ -30,28 +31,6 @@ import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
-private const val STATUS_ACTION = "pan.alexander.tordnscrypt.STATUS_ACTION"
-
-private const val STATUS_ARG = "STATUS"
-private const val STATUS_RUNNING = "RUNNING"
-private const val STATUS_READY = "READY"
-private const val STATUS_STOPPED = "STOPPED"
-private const val STATUS_DISABLED = "CONTROL_DISABLED"
-
-private const val MODULE_ARG = "MODULE"
-private const val DNSCRYPT = "DNSCRYPT"
-private const val TOR = "TOR"
-private const val I2PD = "I2PD"
-
-private const val DNSCRYPT_DNS_PORT_ARG = "DNSCRYPT_DNS_PORT"
-
-private const val TOR_DNS_PORT_ARG = "TOR_DNS_PORT"
-private const val TOR_SOCKS_PROXY_PORT_ARG = "TOR_SOCKS_PROXY_PORT"
-private const val TOR_TRANSPARENT_PROXY_PORT_ARG = "TOR_TRANSPARENT_PROXY_PORT"
-private const val TOR_HTTP_PROXY_PORT_ARG = "TOR_HTTP_PROXY_PORT"
-
-private const val I2PD_HTTP_PROXY_PORT_ARG = "I2PD_HTTP_PROXY_PORT"
-
@Singleton
class ModulesStatusBroadcaster @Inject constructor(
private val context: Context,
@@ -154,6 +133,20 @@ class ModulesStatusBroadcaster @Inject constructor(
}
}
+ fun broadcastFirewallRunning() {
+ getFirewallIntent().also {
+ it.putExtra(STATUS_ARG, STATUS_RUNNING)
+ LocalBroadcastManager.getInstance(context).sendBroadcast(it)
+ }
+ }
+
+ fun broadcastFirewallStopped() {
+ getFirewallIntent().also {
+ it.putExtra(STATUS_ARG, STATUS_STOPPED)
+ LocalBroadcastManager.getInstance(context).sendBroadcast(it)
+ }
+ }
+
private fun getDNSCryptIntent() =
Intent().also {
it.setAction(STATUS_ACTION)
@@ -178,6 +171,12 @@ class ModulesStatusBroadcaster @Inject constructor(
it.putExtra(I2PD_HTTP_PROXY_PORT_ARG, pathVars.itpdHttpProxyPort)
}
+ private fun getFirewallIntent() =
+ Intent().also {
+ it.setAction(STATUS_ACTION)
+ it.putExtra(MODULE_ARG, FIREWALL)
+ }
+
fun broadcastRemoteControlDisabled() {
Intent().also {
it.setAction(STATUS_ACTION)
@@ -190,4 +189,29 @@ class ModulesStatusBroadcaster @Inject constructor(
remoteControlActive = enabled
}
+ companion object {
+ const val STATUS_ACTION = "pan.alexander.tordnscrypt.STATUS_ACTION"
+
+ const val STATUS_ARG = "STATUS"
+ const val STATUS_RUNNING = "RUNNING"
+ const val STATUS_READY = "READY"
+ const val STATUS_STOPPED = "STOPPED"
+ const val STATUS_DISABLED = "CONTROL_DISABLED"
+
+ const val MODULE_ARG = "MODULE"
+ const val DNSCRYPT = "DNSCRYPT"
+ const val TOR = "TOR"
+ const val I2PD = "I2PD"
+ const val FIREWALL = "FIREWALL"
+
+ const val DNSCRYPT_DNS_PORT_ARG = "DNSCRYPT_DNS_PORT"
+
+ const val TOR_DNS_PORT_ARG = "TOR_DNS_PORT"
+ const val TOR_SOCKS_PROXY_PORT_ARG = "TOR_SOCKS_PROXY_PORT"
+ const val TOR_TRANSPARENT_PROXY_PORT_ARG = "TOR_TRANSPARENT_PROXY_PORT"
+ const val TOR_HTTP_PROXY_PORT_ARG = "TOR_HTTP_PROXY_PORT"
+
+ const val I2PD_HTTP_PROXY_PORT_ARG = "I2PD_HTTP_PROXY_PORT"
+ }
+
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt
index 924c0fbd9..76496eba1 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/modules/UsageStatistic.kt
@@ -122,7 +122,7 @@ class UsageStatistic(private val context: Context) {
if (modulesStatus.isDeviceInteractive
&& (title != savedTitle || message != savedMessage)) {
- serviceNotification?.updateNotification(title, message)
+ serviceNotification?.updateNotification(context, title, message, startTime)
savedTitle = title
savedMessage = message
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java
index af920fe5b..184b3d0fe 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PathVars.java
@@ -312,6 +312,10 @@ public String getDNSCryptBlackListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/blacklist.txt";
}
+ public String getDNSCryptSingleBlackListPath() {
+ return appDataDir + "/app_data/dnscrypt-proxy/blacklist-single.txt";
+ }
+
public String getDNSCryptLocalBlackListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/blacklist-local.txt";
}
@@ -324,6 +328,10 @@ public String getDNSCryptIPBlackListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist.txt";
}
+ public String getDNSCryptSingleIPBlackListPath() {
+ return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist-single.txt";
+ }
+
public String getDNSCryptLocalIPBlackListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist-local.txt";
}
@@ -336,6 +344,10 @@ public String getDNSCryptWhiteListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/whitelist.txt";
}
+ public String getDNSCryptSingleWhiteListPath() {
+ return appDataDir + "/app_data/dnscrypt-proxy/whitelist-single.txt";
+ }
+
public String getDNSCryptLocalWhiteListPath() {
return appDataDir + "/app_data/dnscrypt-proxy/whitelist-local.txt";
}
@@ -348,6 +360,10 @@ public String getDNSCryptCloakingRulesPath() {
return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules.txt";
}
+ public String getDNSCryptSingleCloakingRulesPath() {
+ return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules-single.txt";
+ }
+
public String getDNSCryptLocalCloakingRulesPath() {
return appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules-local.txt";
}
@@ -360,6 +376,10 @@ public String getDNSCryptForwardingRulesPath() {
return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules.txt";
}
+ public String getDNSCryptSingleForwardingRulesPath() {
+ return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules-single.txt";
+ }
+
public String getDNSCryptLocalForwardingRulesPath() {
return appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules-local.txt";
}
@@ -472,4 +492,12 @@ public void setAppVersion(T ignoredCaller, String versio
public String getAppProcVersion() {
return appProcVersion;
}
+
+ public String getDNSCryptDefaultForwardingRule() {
+ return "onion 127.0.0.1:" + getTorDNSPort();
+ }
+
+ public String getDNSCryptDefaultCloakingRule() {
+ return "*.i2p 10.191.0.1";
+ }
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java
index dcfb81ed8..7b9298b69 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesCommonFragment.java
@@ -43,7 +43,7 @@
import android.view.View;
import android.view.ViewGroup;
-import java.util.ArrayList;;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -79,6 +79,7 @@
import static pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALWAYS_ON_VPN;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_BLOCK_INTERNET;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_DETECTION;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ARP_SPOOFING_NOT_SUPPORTED;
@@ -180,6 +181,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
}
}
+ Preference alwaysOnVPN = findPreference(ALWAYS_ON_VPN);
+ if (modulesStatus.getMode() == VPN_MODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ && alwaysOnVPN != null) {
+ alwaysOnVPN.setOnPreferenceClickListener(this);
+ } else if (otherCategory != null && alwaysOnVPN != null) {
+ otherCategory.removePreference(alwaysOnVPN);
+ }
Preference swCompatibilityMode = findPreference(COMPATIBILITY_MODE);
if (modulesStatus.getMode() != VPN_MODE && otherCategory != null && swCompatibilityMode != null) {
@@ -485,6 +493,13 @@ public boolean onPreferenceClick(@NonNull Preference preference) {
} catch (Exception e) {
loge("PreferencesCommonFragment startHOTSPOT", e);
}
+ } else if (ALWAYS_ON_VPN.equals(preference.getKey())) {
+ Intent vpnSettingsIntent = new Intent("android.settings.VPN_SETTINGS");
+ try {
+ getActivity().startActivity(vpnSettingsIntent);
+ } catch (Exception e) {
+ loge("PreferencesCommonFragment ALWAYS_ON_VPN", e);
+ }
}
return false;
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java
index f31e3be96..2453fd02b 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsActivity.java
@@ -20,25 +20,18 @@
package pan.alexander.tordnscrypt.settings;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.fragment.app.Fragment;
import android.annotation.SuppressLint;
-import android.content.ClipData;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.Uri;
import android.os.Bundle;
-import androidx.preference.PreferenceManager;
-
import android.view.Menu;
import android.view.MenuItem;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -53,12 +46,13 @@
import pan.alexander.tordnscrypt.settings.dnscrypt_servers.PreferencesDNSCryptServers;
import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment;
import pan.alexander.tordnscrypt.settings.firewall.FirewallFragment;
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType;
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.DnsRulesFragment;
import pan.alexander.tordnscrypt.settings.tor_bridges.PreferencesTorBridges;
-import pan.alexander.tordnscrypt.settings.show_rules.ShowRulesRecycleFrag;
+import pan.alexander.tordnscrypt.settings.itpd_settings.ITPDSubscriptionsFragment;
import pan.alexander.tordnscrypt.settings.tor_apps.UnlockTorAppsFragment;
import pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsFragment;
import pan.alexander.tordnscrypt.settings.tor_preferences.PreferencesTorFragment;
-import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant;
import pan.alexander.tordnscrypt.utils.filemanager.FileManager;
import static pan.alexander.tordnscrypt.settings.tor_ips.UnlockTorIpsFragment.DeviceOrTether.DEVICE;
@@ -75,7 +69,6 @@ public class SettingsActivity extends LangAppCompatActivity {
public static final String tor_conf_tag = "pan.alexander.tordnscrypt/app_data/tor/tor.conf";
public static final String itpd_conf_tag = "pan.alexander.tordnscrypt/app_data/itpd/itpd.conf";
public static final String itpd_tunnels_tag = "pan.alexander.tordnscrypt/app_data/itpd/tunnels.conf";
- public static final String rules_tag = "pan.alexander.tordnscrypt/app_data/abstract_rules";
@Inject
public Lazy preferenceRepository;
@@ -156,45 +149,42 @@ protected void onCreate(Bundle savedInstanceState) {
fSupportTrans.replace(android.R.id.content, frag);
fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "forwarding_rules_Pref")) {
- dialogFragment = PleaseWaitProgressDialog.getInstance();
- dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog");
- FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/forwarding-rules.txt", rules_tag);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.FORWARDING);
+ DnsRulesFragment fragment = new DnsRulesFragment();
+ fragment.setArguments(bundle);
+ fSupportTrans.replace(android.R.id.content, fragment);
+ fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "cloaking_rules_Pref")) {
- dialogFragment = PleaseWaitProgressDialog.getInstance();
- dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog");
- FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/cloaking-rules.txt", rules_tag);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.CLOAKING);
+ DnsRulesFragment fragment = new DnsRulesFragment();
+ fragment.setArguments(bundle);
+ fSupportTrans.replace(android.R.id.content, fragment);
+ fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "blacklist_Pref")) {
- dialogFragment = PleaseWaitProgressDialog.getInstance();
- dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog");
- FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/blacklist.txt", rules_tag);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.BLACKLIST);
+ DnsRulesFragment fragment = new DnsRulesFragment();
+ fragment.setArguments(bundle);
+ fSupportTrans.replace(android.R.id.content, fragment);
+ fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "ipblacklist_Pref")) {
- dialogFragment = PleaseWaitProgressDialog.getInstance();
- dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog");
- FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/ip-blacklist.txt", rules_tag);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.IP_BLACKLIST);
+ DnsRulesFragment fragment = new DnsRulesFragment();
+ fragment.setArguments(bundle);
+ fSupportTrans.replace(android.R.id.content, fragment);
+ fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "whitelist_Pref")) {
- dialogFragment = PleaseWaitProgressDialog.getInstance();
- dialogFragment.show(getSupportFragmentManager(), "PleaseWaitProgressDialog");
- FileManager.readTextFile(this, appDataDir + "/app_data/dnscrypt-proxy/whitelist.txt", rules_tag);
- } else if (Objects.equals(intent.getAction(), "pref_itpd_addressbook_subscriptions")) {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- ArrayList rules_file = new ArrayList<>();
-
- String subscriptionsSaved = sp.getString("subscriptions", "");
-
- String[] arr = {""};
- if (subscriptionsSaved != null && subscriptionsSaved.contains(",")) {
- arr = subscriptionsSaved.split(",");
- }
-
- String subscriptions = "subscriptions";
- for (String str : arr) {
- rules_file.add(str.trim());
- }
Bundle bundle = new Bundle();
- bundle.putStringArrayList("rules_file", rules_file);
- bundle.putString("path", subscriptions);
- ShowRulesRecycleFrag frag = new ShowRulesRecycleFrag();
- frag.setArguments(bundle);
+ bundle.putSerializable(DnsRulesFragment.RULE_TYPE_ARG, DnsRuleType.WHITELIST);
+ DnsRulesFragment fragment = new DnsRulesFragment();
+ fragment.setArguments(bundle);
+ fSupportTrans.replace(android.R.id.content, fragment);
+ fSupportTrans.commit();
+ } else if (Objects.equals(intent.getAction(), "pref_itpd_addressbook_subscriptions")) {
+ ITPDSubscriptionsFragment frag = new ITPDSubscriptionsFragment();
fSupportTrans.replace(android.R.id.content, frag);
fSupportTrans.commit();
} else if (Objects.equals(intent.getAction(), "tor_sites_unlock")) {
@@ -243,60 +233,6 @@ public void onAttachFragment(@NonNull Fragment fragment) {
}
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode != RESULT_OK) {
- return;
- }
-
- if (preferencesDNSFragment != null && data != null) {
- Uri[] filesUri = getFilesUri(data);
-
- if (filesUri.length > 0) {
-
- switch (requestCode) {
- case PreferencesDNSFragment.PICK_BLACKLIST_HOSTS:
- preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.BLACKLIST_HOSTS, filesUri);
- break;
- case PreferencesDNSFragment.PICK_WHITELIST_HOSTS:
- preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.WHITELIST_HOSTS, filesUri);
- break;
- case PreferencesDNSFragment.PICK_BLACKLIST_IPS:
- preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.BLACKLIST_IPS, filesUri);
- break;
- case PreferencesDNSFragment.PICK_FORWARDING:
- preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.FORWARDING, filesUri);
- break;
- case PreferencesDNSFragment.PICK_CLOAKING:
- preferencesDNSFragment.importRules(this, DNSCryptRulesVariant.CLOAKING, filesUri);
- break;
- default:
- loge("SettingsActivity wrong onActivityRequestCode " + requestCode);
- }
-
- }
- }
- }
-
- private Uri[] getFilesUri(Intent data) {
- Uri[] uris = new Uri[0];
- ClipData clipData = data.getClipData();
-
- if (clipData == null && data.getData() != null) {
- uris = new Uri[]{data.getData()};
- } else if (clipData != null) {
- uris = new Uri[clipData.getItemCount()];
- for (int i = 0; i < clipData.getItemCount(); i++) {
- Uri uri = clipData.getItemAt(i).getUri();
- uris[i] = uri;
- }
- }
-
- return uris;
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java
index fd73727a5..da63b9dcd 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/SettingsParser.java
@@ -33,7 +33,7 @@
import pan.alexander.tordnscrypt.R;
import pan.alexander.tordnscrypt.settings.dnscrypt_settings.PreferencesDNSFragment;
-import pan.alexander.tordnscrypt.settings.show_rules.ShowRulesRecycleFrag;
+import pan.alexander.tordnscrypt.settings.itpd_settings.PreferencesITPDFragment;
import pan.alexander.tordnscrypt.settings.tor_preferences.PreferencesTorFragment;
import pan.alexander.tordnscrypt.utils.enums.FileOperationsVariants;
import pan.alexander.tordnscrypt.utils.filemanager.FileManager;
@@ -44,7 +44,6 @@
import static pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_WITH_MASK;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_BOOTSTRAP_RESOLVERS;
-import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_DNS64_PREFIX;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_LISTEN_PORT;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_OUTBOUND_PROXY;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DORMANT_CLIENT_TIMEOUT;
@@ -430,27 +429,6 @@ private void readITPDconf(List lines) {
}
}
- private void readRules(String path, List lines) {
- ArrayList rules_file = new ArrayList<>();
- if (lines != null) {
- rules_file.addAll(lines);
- } else {
- rules_file.add("");
- }
- FragmentManager manager = settingsActivity.getSupportFragmentManager();
- if (manager.isDestroyed()) {
- return;
- }
- FragmentTransaction fTrans = manager.beginTransaction();
- Bundle bundle = new Bundle();
- bundle.putStringArrayList("rules_file", rules_file);
- bundle.putString("path", path);
- ShowRulesRecycleFrag frag = new ShowRulesRecycleFrag();
- frag.setArguments(bundle);
- fTrans.replace(android.R.id.content, frag);
- fTrans.commit();
- }
-
public void activateSettingsParser() {
FileManager.setOnFileOperationCompleteListener(this);
}
@@ -483,17 +461,10 @@ public void OnFileOperationComplete(FileOperationsVariants currentFileOperation,
case SettingsActivity.itpd_conf_tag:
readITPDconf(lines);
break;
- case SettingsActivity.rules_tag:
- readRules(path, lines);
- break;
}
});
- } else if (!fileOperationResult && currentFileOperation == readTextFile) {
- if (tag.equals(SettingsActivity.rules_tag)) {
- readRules(path, lines);
- }
} else if (fileOperationResult && currentFileOperation == writeToTextFile) {
settingsActivity.runOnUiThread(() -> Toast.makeText(settingsActivity, settingsActivity.getText(R.string.toastSettings_saved), Toast.LENGTH_SHORT).show());
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt
new file mode 100644
index 000000000..d7af4f985
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/AddRemoteRulesUrlDialog.kt
@@ -0,0 +1,87 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules
+
+import android.content.Context
+import android.text.Editable
+import android.text.TextWatcher
+import android.widget.EditText
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.core.view.setPadding
+import pan.alexander.tordnscrypt.R
+import pan.alexander.tordnscrypt.utils.Constants.URL_REGEX
+import pan.alexander.tordnscrypt.utils.Utils.dp2pixels
+import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl
+import java.util.regex.Pattern
+
+class AddRemoteRulesUrlDialog {
+
+ var callback: OnAddRemoteRulesUrl? = null
+
+ fun createDialog(context: Context) =
+ AlertDialog.Builder(context)
+ .apply {
+ val urlPattern = Pattern.compile(URL_REGEX)
+ val editText = EditText(context).apply {
+ setPadding(dp2pixels(8).toInt())
+ addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(
+ s: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(
+ s: CharSequence?,
+ start: Int,
+ before: Int,
+ count: Int
+ ) {
+ }
+
+ override fun afterTextChanged(s: Editable?) {
+ if (urlPattern.matcher(s?.toString() ?: "").matches()) {
+ setTextColor(ContextCompat.getColor(context, R.color.colorText))
+ } else {
+ setTextColor(ContextCompat.getColor(context, R.color.colorAlert))
+ }
+ }
+
+ })
+ }
+ setView(editText)
+ setTitle(R.string.dns_rule_add_url)
+ setPositiveButton(R.string.ok) { _, _ ->
+ val url = editText.text?.toString() ?: ""
+ if (urlPattern.matcher(url).matches()) {
+ val name = getDomainNameFromUrl(url)
+ callback?.onRemoteRulesUrlAdded(url, name)
+ }
+ }
+ setNegativeButton(R.string.cancel) { _, _ -> }
+ }.create()
+
+ interface OnAddRemoteRulesUrl {
+ fun onRemoteRulesUrlAdded(url: String, name: String)
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt
new file mode 100644
index 000000000..2c57447bc
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesFragment.kt
@@ -0,0 +1,404 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.github.angads25.filepicker.model.DialogConfigs
+import com.github.angads25.filepicker.model.DialogProperties
+import com.github.angads25.filepicker.view.FilePickerDialog
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import pan.alexander.tordnscrypt.App
+import pan.alexander.tordnscrypt.R
+import pan.alexander.tordnscrypt.databinding.FragmentDnsRuleBinding
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRulesRecyclerAdapter
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.receiver.DnsRulesReceiver
+import java.io.File
+import java.util.Date
+import javax.inject.Inject
+
+private const val TOTAL_RULES_WARNING_COUNT = 400000
+private const val TOTAL_RULES_ALERT_COUNT = 800000
+
+class DnsRulesFragment : Fragment(), DnsRulesReceiver.Callback,
+ AddRemoteRulesUrlDialog.OnAddRemoteRulesUrl {
+
+ @Inject
+ lateinit var pathVars: dagger.Lazy
+
+ @Inject
+ lateinit var dnsRulesReceiver: dagger.Lazy
+
+ @Inject
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+ private val viewModel by lazy {
+ ViewModelProvider(this, viewModelFactory)[DnsRulesViewModel::class.java]
+ }
+
+ private var _binding: FragmentDnsRuleBinding? = null
+ private val binding get() = _binding!!
+
+ private var rulesAdapter: DnsRulesRecyclerAdapter? = null
+
+ @Suppress("deprecation")
+ private val ruleType by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ arguments?.getSerializable(RULE_TYPE_ARG, DnsRuleType::class.java)
+ } else {
+ arguments?.getSerializable(RULE_TYPE_ARG) as DnsRuleType
+ }
+ }
+
+ private var importFilesLauncher: ActivityResultLauncher>? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ App.instance.daggerComponent.inject(this)
+ super.onCreate(savedInstanceState)
+
+ importFilesLauncher =
+ activity?.registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) {
+ importRulesCommon(it.toTypedArray())
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+
+ _binding = try {
+ FragmentDnsRuleBinding.inflate(inflater, container, false)
+ } catch (e: Exception) {
+ loge("DnsRulesFragment onCreateView", e)
+ throw e
+ }
+
+ ruleType?.let {
+ setTitle(it)
+ }
+
+ initRulesRecycler()
+
+ return binding.root
+ }
+
+ private fun setTitle(ruleType: DnsRuleType) {
+ when (ruleType) {
+ DnsRuleType.BLACKLIST -> requireActivity().setTitle(R.string.title_dnscrypt_blacklist)
+ DnsRuleType.WHITELIST -> requireActivity().setTitle(R.string.title_dnscrypt_whitelist)
+ DnsRuleType.IP_BLACKLIST -> requireActivity().setTitle(R.string.title_dnscrypt_ip_blacklist)
+ DnsRuleType.FORWARDING -> requireActivity().setTitle(R.string.title_dnscrypt_forwarding_rules)
+ DnsRuleType.CLOAKING -> requireActivity().setTitle(R.string.title_dnscrypt_cloaking_rules)
+ }
+ }
+
+ private fun initRulesRecycler() {
+ rulesAdapter = DnsRulesRecyclerAdapter(
+ ::importLocalRules,
+ ::deleteLocalRules,
+ ::deltaTotalRules,
+ ::addRemoteRules,
+ ::deleteRemoteRules,
+ ::refreshRemoteRules
+ ).also {
+ it.rulesType = ruleType
+ }
+ binding.rvDnsRules.layoutManager = LinearLayoutManager(context)
+ binding.rvDnsRules.adapter = rulesAdapter
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ registerReceivers()
+ observeRules()
+
+ if (savedInstanceState == null) {
+ requestRules()
+ }
+ }
+
+ private fun registerReceivers() = with(dnsRulesReceiver.get()) {
+ callback = this@DnsRulesFragment
+ dnsRulesReceiver.get().registerReceiver()
+ }
+
+ private fun requestRules() {
+ ruleType?.let {
+ viewModel.requestRules(it)
+ showProgressIndicator()
+ }
+ }
+
+ private fun observeRules() {
+ lifecycleScope.launch {
+ viewModel.dnsRulesStateFlow
+ .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
+ .collectLatest {
+ it?.let { rules ->
+ updateRules(rules)
+ }.also {
+ updateTotalRules(viewModel.totalRulesCount.get())
+ }
+ }
+ }
+ }
+
+ private suspend fun updateRules(rules: List) {
+ while (binding.rvDnsRules.isComputingLayout) {
+ delay(300)
+ }
+ hideProgressIndicator()
+ rulesAdapter?.updateRules(rules)
+ hideProgressIndicator()
+ }
+
+
+ private fun deltaTotalRules(delta: Int) {
+ updateTotalRules(viewModel.totalRulesCount.addAndGet(delta))
+ }
+
+ private fun updateTotalRules(count: Int) {
+ binding.tvDnsRuleTotalQuantity.text = String.format(
+ getString(R.string.total_rules),
+ count
+ )
+ when (count) {
+ in 0..TOTAL_RULES_WARNING_COUNT -> {
+ binding.tvDnsRuleTotalQuantity.setBackgroundColor(
+ ContextCompat.getColor(requireContext(), R.color.colorGreen)
+ )
+ }
+
+ in TOTAL_RULES_WARNING_COUNT..TOTAL_RULES_ALERT_COUNT -> {
+ binding.tvDnsRuleTotalQuantity.setBackgroundColor(
+ ContextCompat.getColor(requireContext(), R.color.colorOrange)
+ )
+ }
+
+ else -> {
+ binding.tvDnsRuleTotalQuantity.setBackgroundColor(
+ ContextCompat.getColor(requireContext(), R.color.colorAlert)
+ )
+ }
+ }
+ }
+
+ private fun addRemoteRules() {
+ val context = context ?: return
+ getAddUrlDialog(context).show()
+ }
+
+ private fun getAddUrlDialog(context: Context): AlertDialog =
+ AddRemoteRulesUrlDialog().also {
+ it.callback = this
+ }.createDialog(context)
+
+ override fun onRemoteRulesUrlAdded(url: String, name: String) {
+ saveRemoteRulesUrl(url)
+ rulesAdapter?.updateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = name,
+ url = url,
+ date = Date(),
+ count = 0,
+ size = 0,
+ inProgress = true
+ )
+ )
+ processRemoteRules(name)
+ binding.rvDnsRules.smoothScrollToPosition(0)
+ }
+
+ private fun saveRemoteRulesUrl(url: String) {
+ val ruleType = ruleType ?: return
+ viewModel.saveRemoteRulesUrl(ruleType, url)
+ }
+
+ private fun processRemoteRules(name: String) {
+ val ruleType = ruleType ?: return
+ val currentRules = getCurrentRules() ?: return
+ viewModel.updateRemoteRules(ruleType, currentRules, name)
+ }
+
+ private fun refreshRemoteRules() {
+ val ruleType = ruleType ?: return
+ val currentRules = getCurrentRules() ?: return
+ val remoteRules = getCurrentRemoteRules() ?: return
+ viewModel.updateRemoteRules(ruleType, currentRules, remoteRules.name)
+ }
+
+ private fun deleteRemoteRules() {
+ val ruleType = ruleType ?: return
+ val currentRules = getCurrentRules() ?: return
+ viewModel.deleteRemoteRules(ruleType, currentRules)
+ }
+
+ private fun importLocalRules() {
+ lifecycleScope.launch {
+ if (viewModel.isExternalStorageAllowsDirectAccess()) {
+ importRulesWithFilePicker()
+ } else {
+ importRulesWithSAF()
+ }
+ }
+ }
+
+ private fun importRulesWithFilePicker() {
+ val activity = activity ?: return
+ val dialog = FilePickerDialog(
+ activity,
+ getFilePickerProperties(activity)
+ )
+ dialog.setDialogSelectionListener {
+ importRulesCommon(it)
+ }
+ dialog.show()
+ }
+
+ private fun importRulesWithSAF() {
+ try {
+ importFilesLauncher?.launch(arrayOf("text/plain"))
+ } catch (e: Exception) {
+ loge("DnsRulesFragment importRulesWithSAF", e)
+ }
+ }
+
+ private fun importRulesCommon(files: Array<*>) {
+ val ruleType = ruleType ?: return
+ val currentRules = getCurrentRules() ?: return
+ viewModel.importLocalRules(ruleType, currentRules, files)
+ }
+
+ private fun getFilePickerProperties(context: Context): DialogProperties =
+ DialogProperties().apply {
+ selection_mode = DialogConfigs.MULTI_MODE
+ selection_type = DialogConfigs.FILE_SELECT
+ root =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ error_dir = File(pathVars.get().getCacheDirPath(context))
+ offset =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ extensions = arrayOf("txt")
+ }
+
+ private fun deleteLocalRules() {
+ val ruleType = ruleType ?: return
+ val currentRules = getCurrentRules() ?: return
+ viewModel.deleteLocalRules(ruleType, currentRules)
+ }
+
+ private fun getCurrentRules() = rulesAdapter?.getRules()?.toList()
+
+ private fun getCurrentRemoteRules() = rulesAdapter?.getRules()?.find {
+ it is DnsRuleRecycleItem.DnsRemoteRule
+ } as? DnsRuleRecycleItem.DnsRemoteRule
+
+ override fun onPause() {
+ super.onPause()
+
+ val activity = activity ?: return
+
+ getCurrentRules()?.let {
+ if (activity.isFinishing) {
+ saveRulesPersistent(it)
+ } else {
+ saveRulesTemporarily(it)
+ }
+ }
+ }
+
+ private fun saveRulesTemporarily(rules: List) {
+ viewModel.saveTemporarily(rules)
+ }
+
+ private fun saveRulesPersistent(rules: List) {
+ val ruleType = ruleType ?: return
+ viewModel.saveRules(ruleType, rules)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+
+ unregisterReceiver()
+ _binding = null
+ rulesAdapter = null
+ }
+
+ private fun unregisterReceiver() = with(dnsRulesReceiver.get()) {
+ callback = null
+ unregisterReceiver()
+ }
+
+ private fun showProgressIndicator() {
+ binding.pbDnsRules.visibility = View.VISIBLE
+ }
+
+ private fun hideProgressIndicator() {
+ binding.pbDnsRules.visibility = View.GONE
+ }
+
+ override fun onUpdateRemoteRules(rules: DnsRuleRecycleItem.DnsRemoteRule) {
+ rulesAdapter?.updateRemoteRules(rules)
+ }
+
+ override fun onUpdateLocalRules(rules: DnsRuleRecycleItem.DnsLocalRule) {
+ rulesAdapter?.updateLocalRules(rules)
+ }
+
+ override fun onUpdateTotalRules(count: Int) {
+ _binding?.let {
+ viewModel.totalRulesCount.set(count)
+ updateTotalRules(count)
+ }
+ }
+
+ override fun onUpdateFinished() {
+ val ruleType = ruleType ?: return
+ viewModel.requestRules(ruleType)
+ }
+
+ companion object {
+ const val RULE_TYPE_ARG = "pan.alexander.tordnscrypt.RULE_TYPE_ARG"
+ }
+
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt
new file mode 100644
index 000000000..8ce123eb6
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/DnsRulesViewModel.kt
@@ -0,0 +1,256 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
+import pan.alexander.tordnscrypt.di.CoroutinesModule.Companion.SUPERVISOR_JOB_IO_DISPATCHER_SCOPE
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRulesInteractor
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL
+import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
+import javax.inject.Named
+
+class DnsRulesViewModel @Inject constructor(
+ private val interactor: DnsRulesInteractor,
+ @Named(SUPERVISOR_JOB_IO_DISPATCHER_SCOPE)
+ private val baseCoroutineScope: CoroutineScope,
+ private val preferences: dagger.Lazy,
+ private val remixExistingRulesWorkManager: dagger.Lazy,
+ private val updateRemoteDnsRulesManager: dagger.Lazy,
+ private val updateLocalRulesWorkManager: dagger.Lazy
+) : ViewModel() {
+ private val supervisorScope = baseCoroutineScope + CoroutineName("DnsRulesViewModel")
+
+ private val dnsRulesMutableStateFlow: MutableStateFlow?> =
+ MutableStateFlow(null)
+ val dnsRulesStateFlow: StateFlow?> get() = dnsRulesMutableStateFlow
+
+ val totalRulesCount = AtomicInteger(0)
+
+ fun requestRules(rulesType: DnsRuleType) {
+ viewModelScope.launch {
+ try {
+ tryRequestRules(rulesType)
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel requestRules", e)
+ }
+ }
+
+ }
+
+ private suspend fun tryRequestRules(rulesType: DnsRuleType) {
+ val totalRulesCount = interactor.getMixedRulesMetadata(rulesType).count
+
+ val singleRules = interactor.getSingleRules(rulesType)
+
+ this@DnsRulesViewModel.totalRulesCount.set(totalRulesCount)
+
+ dnsRulesMutableStateFlow.value = mutableListOf().also {
+
+ var displayedRemoteRulesLastIndex = -1
+ val remoteRulesMetadata = interactor.getRemoteRulesMetadata(rulesType)
+ if (remoteRulesMetadata.count > 0) {
+ displayedRemoteRulesLastIndex = 0
+ it.add(
+ displayedRemoteRulesLastIndex,
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = remoteRulesMetadata.name,
+ url = remoteRulesMetadata.url,
+ date = remoteRulesMetadata.date,
+ count = remoteRulesMetadata.count,
+ size = remoteRulesMetadata.size,
+ inProgress = false
+ )
+ )
+ }
+ val displayedAddButtonRemoteRulesLastIndex = displayedRemoteRulesLastIndex + 1
+ it.add(
+ displayedAddButtonRemoteRulesLastIndex,
+ DnsRuleRecycleItem.AddRemoteRulesButton
+ )
+
+ val displayedAddButtonLocalRulesLastIndex: Int
+ val localRulesMetadata = interactor.getLocalRulesMetadata(rulesType)
+ if (localRulesMetadata.count > 0) {
+ val displayedLocalRulesLastIndex = displayedAddButtonRemoteRulesLastIndex + 1
+ it.add(
+ displayedLocalRulesLastIndex,
+ DnsRuleRecycleItem.DnsLocalRule(
+ name = localRulesMetadata.name,
+ date = localRulesMetadata.date,
+ count = localRulesMetadata.count,
+ size = localRulesMetadata.size,
+ inProgress = false
+ )
+ )
+ displayedAddButtonLocalRulesLastIndex = displayedLocalRulesLastIndex + 1
+ it.add(
+ displayedAddButtonLocalRulesLastIndex,
+ DnsRuleRecycleItem.AddLocalRulesButton
+ )
+ } else {
+ displayedAddButtonLocalRulesLastIndex = displayedAddButtonRemoteRulesLastIndex + 1
+ it.add(
+ displayedAddButtonLocalRulesLastIndex,
+ DnsRuleRecycleItem.AddLocalRulesButton
+ )
+ }
+
+
+ it.addAll(singleRules)
+
+ it.add(DnsRuleRecycleItem.AddSingleRuleButton)
+ }
+ }
+
+ fun updateRemoteRules(
+ ruleType: DnsRuleType,
+ currentRules: List,
+ name: String
+ ) {
+ supervisorScope.launch {
+ try {
+ saveTemporarily(currentRules)
+ saveSingleRules(ruleType, getSingleRules(currentRules))
+ updateRemoteDnsRulesManager.get().startRefreshDnsRules(name, ruleType)
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel updateRemoteRules", e)
+ }
+ }
+ }
+
+ fun deleteRemoteRules(ruleType: DnsRuleType, currentRules: List) {
+ supervisorScope.launch {
+ try {
+ saveTemporarily(currentRules)
+ updateRemoteDnsRulesManager.get().stopRefreshDnsRules(ruleType)
+ saveSingleRules(ruleType, getSingleRules(currentRules))
+ delay(500)
+ interactor.clearRemoteRules(ruleType)
+ remixExistingRulesWorkManager.get().startMix(ruleType)
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel deleteRemoteRules", e)
+ }
+ }
+ }
+
+ fun importLocalRules(
+ ruleType: DnsRuleType,
+ currentRules: List,
+ files: Array<*>
+ ) {
+ supervisorScope.launch {
+ try {
+ saveTemporarily(currentRules)
+ saveSingleRules(ruleType, getSingleRules(currentRules))
+ updateLocalRulesWorkManager.get().startImportDnsRules(ruleType, files)
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel importLocalRules", e)
+ }
+ }
+ }
+
+ fun deleteLocalRules(ruleType: DnsRuleType, currentRules: List) {
+ supervisorScope.launch {
+ try {
+ saveTemporarily(currentRules)
+ updateLocalRulesWorkManager.get().stopImportDnsRules(ruleType)
+ saveSingleRules(ruleType, getSingleRules(currentRules))
+ delay(500)
+ interactor.clearLocalRules(ruleType)
+ remixExistingRulesWorkManager.get().startMix(ruleType)
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel deleteLocalRules", e)
+ }
+ }
+ }
+
+ private fun getSingleRules(rules: List) =
+ rules.filterIsInstance()
+
+ fun saveTemporarily(rules: List) {
+ val savedRules = dnsRulesMutableStateFlow.value
+ if (savedRules == null || rules.size != savedRules.size || !rules.containsAll(savedRules)) {
+ dnsRulesMutableStateFlow.value = rules
+ }
+ }
+
+ fun saveRules(ruleType: DnsRuleType, rules: List) {
+ supervisorScope.launch {
+ try {
+ val saved = saveSingleRules(ruleType, getSingleRules(rules))
+ if (saved) {
+ remixExistingRulesWorkManager.get().startMix(ruleType)
+ }
+ } catch (e: Exception) {
+ loge("DnsRulesViewModel saveRules", e)
+ }
+ }
+ }
+
+ fun saveRemoteRulesUrl(ruleType: DnsRuleType, url: String) = with(preferences.get()) {
+ val urlToSave = if (url.startsWith("http")) {
+ url
+ } else {
+ "https://$url"
+ }
+ when (ruleType) {
+ DnsRuleType.BLACKLIST -> setStringPreference(REMOTE_BLACKLIST_URL, urlToSave)
+ DnsRuleType.WHITELIST -> setStringPreference(REMOTE_WHITELIST_URL, urlToSave)
+ DnsRuleType.IP_BLACKLIST -> setStringPreference(REMOTE_IP_BLACKLIST_URL, urlToSave)
+ DnsRuleType.FORWARDING -> setStringPreference(REMOTE_FORWARDING_URL, urlToSave)
+ DnsRuleType.CLOAKING -> setStringPreference(REMOTE_CLOAKING_URL, urlToSave)
+ }
+ }
+
+ private suspend fun saveSingleRules(
+ ruleType: DnsRuleType,
+ rules: List,
+ ): Boolean {
+ val singleRules = interactor.getSingleRules(ruleType)
+ if (singleRules.size != rules.size || !singleRules.containsAll(rules)) {
+ interactor.saveSingleRules(ruleType, rules)
+ return true
+ }
+ return false
+ }
+
+ suspend fun isExternalStorageAllowsDirectAccess() =
+ interactor.isExternalStorageAllowsDirectAccess()
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt
new file mode 100644
index 000000000..08bbea506
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingDnsRulesWorker.kt
@@ -0,0 +1,122 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runInterruptible
+import pan.alexander.tordnscrypt.App
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_WHITELIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_WHITELIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_RULES_TYPE_ARG
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import javax.inject.Inject
+import javax.inject.Named
+
+class RemixExistingDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ init {
+ App.instance.daggerComponent.inject(this)
+ }
+
+ @Inject
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ lateinit var dispatcherIo: CoroutineDispatcher
+
+ override suspend fun doWork(): Result {
+ try {
+ val ruleType = inputData.getString(MIX_RULES_TYPE_ARG)?.let {
+ DnsRuleType.valueOf(it)
+ } ?: return Result.failure()
+
+ while (isRemoteDnsRulesImportingInProgress(ruleType)
+ || isLocalDnsRulesImportingInProgress(ruleType)
+ ) {
+ delay(500)
+ }
+
+ updateRules(ruleType)
+
+ return Result.success()
+ } catch (e: Exception) {
+ loge("UpdateSingleDnsRulesWorker doWork", e)
+ }
+ return Result.failure()
+ }
+
+ private suspend fun updateRules(
+ ruleType: DnsRuleType,
+ ) = runInterruptible(dispatcherIo) {
+ ImportRulesManager(
+ context = appContext,
+ rulesVariant = ruleType,
+ importType = ImportRulesManager.ImportType.SINGLE_RULES,
+ filePathToImport = emptyArray()
+ ).run()
+ }
+
+ private fun isRemoteDnsRulesImportingInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getRemoteWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+
+ private fun getRemoteWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK
+ }
+
+ private fun isLocalDnsRulesImportingInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getLocalWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+
+ private fun getLocalWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK
+ }
+
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt
new file mode 100644
index 000000000..4ef6f3afb
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/existing/RemixExistingRulesWorkManager.kt
@@ -0,0 +1,94 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing
+
+import android.content.Context
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import androidx.work.workDataOf
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class RemixExistingRulesWorkManager @Inject constructor(
+ private val context: Context,
+) {
+
+ fun startMix(ruleType: DnsRuleType) {
+
+ val constraints = Constraints.Builder()
+ .setRequiresStorageNotLow(true)
+ .build()
+
+ val mixRequest = OneTimeWorkRequestBuilder()
+ .setConstraints(constraints)
+ .setBackoffCriteria(
+ BackoffPolicy.EXPONENTIAL,
+ WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ .setInputData(
+ workDataOf(
+ MIX_RULES_TYPE_ARG to ruleType.name,
+ )
+ )
+ .build()
+
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(
+ getWorkName(ruleType),
+ ExistingWorkPolicy.REPLACE,
+ mixRequest
+ )
+ }
+
+ fun stopMix(type: DnsRuleType) {
+ WorkManager.getInstance(context)
+ .cancelUniqueWork(getWorkName(type))
+ }
+
+ private fun getWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK
+ }
+
+ companion object {
+ const val MIX_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.SINGLE_RULES_TYPE_ARG"
+
+ const val MIX_DNS_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.MIX_DNS_BLACKLIST_WORK"
+ const val MIX_DNS_WHITELIST_WORK =
+ "pan.alexander.tordnscrypt.MIX_DNS_WHITELIST_WORK"
+ const val MIX_DNS_IP_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.MIX_DNS_IP_BLACKLIST_WORK"
+ const val MIX_DNS_FORWARDING_WORK =
+ "pan.alexander.tordnscrypt.MIX_DNS_FORWARDING_WORK"
+ const val MIX_DNS_CLOAKING_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_SINGLE_DNS_CLOAKING_WORK"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt
new file mode 100644
index 000000000..af306e92b
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/DnsRulesUpdateProgress.kt
@@ -0,0 +1,47 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class DnsRulesUpdateProgress : Parcelable {
+
+ data class UpdateProgress(
+ val name: String,
+ val url: String? = null,
+ val size: Long,
+ val count: Int
+ ): DnsRulesUpdateProgress()
+
+ data class UpdateFinished(
+ val name: String,
+ val url: String? = null,
+ val size: Long,
+ val count: Int
+ ): DnsRulesUpdateProgress()
+
+ data class UpdateFailure(
+ val name: String,
+ val url: String? = null,
+ val error: String
+ ): DnsRulesUpdateProgress()
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt
new file mode 100644
index 000000000..bb7663107
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/ImportRulesManager.kt
@@ -0,0 +1,726 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.OpenableColumns
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import pan.alexander.tordnscrypt.App
+import pan.alexander.tordnscrypt.modules.ModulesRestarter
+import pan.alexander.tordnscrypt.modules.ModulesStatus
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.blackListHostRulesRegex
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.blacklistIPRulesRegex
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.cloakingRulesRegex
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.forwardingRulesRegex
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.hostFileRegex
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.DnsRulesRegex.Companion.whiteListHostRulesRegex
+import pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS
+import pan.alexander.tordnscrypt.utils.enums.ModuleState
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.utils.wakelock.WakeLocksManager
+import java.io.BufferedReader
+import java.io.File
+import java.io.InputStreamReader
+import java.io.PrintWriter
+import java.util.concurrent.locks.ReentrantLock
+
+private val excludeFromHost = listOf("localhost", "localhost.localdomain", "local", META_ADDRESS)
+private val reentrantLock = ReentrantLock()
+private val wakeLocksManager = WakeLocksManager.getInstance()
+
+class ImportRulesManager(
+ private val context: Context,
+ private var rulesVariant: DnsRuleType,
+ private var remoteRulesUrl: String? = null,
+ private var remoteRulesName: String? = null,
+ private val importType: ImportType,
+ private val filePathToImport: Array<*>
+) : Runnable {
+
+ private val localBroadcastManager by lazy {
+ LocalBroadcastManager.getInstance(context)
+ }
+
+ private val pathVars: PathVars = App.instance.daggerComponent.getPathVars().get()
+
+ private val blackListHostRulesPath = pathVars.dnsCryptBlackListPath
+ private val blackListHostSingleRulesPath = pathVars.dnsCryptSingleBlackListPath
+ private val blackListHostRulesLocalPath = pathVars.dnsCryptLocalBlackListPath
+ private val blackListHostRulesRemotePath = pathVars.dnsCryptRemoteBlackListPath
+
+ private val blackListIPRulesPath = pathVars.dnsCryptIPBlackListPath
+ private val blackListIPRulesLocalPath = pathVars.dnsCryptLocalIPBlackListPath
+ private val blackListSingleIPRulesPath = pathVars.dnsCryptSingleIPBlackListPath
+ private val blackListIPRulesRemotePath = pathVars.dnsCryptRemoteIPBlackListPath
+
+ private val whiteListHostRulesPath = pathVars.dnsCryptWhiteListPath
+ private val whiteListSingleRulesPath = pathVars.dnsCryptSingleWhiteListPath
+ private val whiteListHostRulesLocalPath = pathVars.dnsCryptLocalWhiteListPath
+ private val whiteListHostRulesRemotePath = pathVars.dnsCryptRemoteWhiteListPath
+
+ private val cloakingRulesPath = pathVars.dnsCryptCloakingRulesPath
+ private val cloakingSingleRulesPath = pathVars.dnsCryptSingleCloakingRulesPath
+ private val cloakingRulesLocalPath = pathVars.dnsCryptLocalCloakingRulesPath
+ private val cloakingRulesRemotePath = pathVars.dnsCryptRemoteCloakingRulesPath
+
+ private val forwardingRulesPath = pathVars.dnsCryptForwardingRulesPath
+ private val forwardingSingleRulesPath = pathVars.dnsCryptSingleForwardingRulesPath
+ private val forwardingRulesLocalPath = pathVars.dnsCryptLocalForwardingRulesPath
+ private val forwardingRulesRemotePath = pathVars.dnsCryptRemoteForwardingRulesPath
+
+ private val defaultForwardingRule = pathVars.dnsCryptDefaultForwardingRule
+ private val defaultCloakingRule = pathVars.dnsCryptDefaultCloakingRule
+
+ private val contentResolver = context.applicationContext.contentResolver
+
+ private var hashes = hashSetOf()
+ private var savedTime = System.currentTimeMillis()
+
+ private var powerLocked = false
+
+ private var rulesFilePath: String = ""
+ private var singleRulesFilePath: String = ""
+ private var localRulesFilePath: String = ""
+ private var remoteRulesFilePath: String = ""
+ private var rulesRegex: Regex = blackListHostRulesRegex
+
+ @Volatile
+ private var importedLinesCount = 0
+
+ @Volatile
+ private var totalLinesCount = 0
+ private var fileNames = ""
+
+
+ override fun run() {
+
+ when (rulesVariant) {
+ DnsRuleType.BLACKLIST -> {
+ rulesFilePath = blackListHostRulesPath
+ singleRulesFilePath = blackListHostSingleRulesPath
+ localRulesFilePath = blackListHostRulesLocalPath
+ remoteRulesFilePath = blackListHostRulesRemotePath
+ rulesRegex = blackListHostRulesRegex
+ }
+
+ DnsRuleType.WHITELIST -> {
+ rulesFilePath = whiteListHostRulesPath
+ singleRulesFilePath = whiteListSingleRulesPath
+ localRulesFilePath = whiteListHostRulesLocalPath
+ remoteRulesFilePath = whiteListHostRulesRemotePath
+ rulesRegex = whiteListHostRulesRegex
+ }
+
+ DnsRuleType.IP_BLACKLIST -> {
+ rulesFilePath = blackListIPRulesPath
+ singleRulesFilePath = blackListSingleIPRulesPath
+ localRulesFilePath = blackListIPRulesLocalPath
+ remoteRulesFilePath = blackListIPRulesRemotePath
+ rulesRegex = blacklistIPRulesRegex
+ }
+
+ DnsRuleType.CLOAKING -> {
+ rulesFilePath = cloakingRulesPath
+ singleRulesFilePath = cloakingSingleRulesPath
+ localRulesFilePath = cloakingRulesLocalPath
+ remoteRulesFilePath = cloakingRulesRemotePath
+ rulesRegex = cloakingRulesRegex
+ }
+
+ DnsRuleType.FORWARDING -> {
+ rulesFilePath = forwardingRulesPath
+ singleRulesFilePath = forwardingSingleRulesPath
+ localRulesFilePath = forwardingRulesLocalPath
+ remoteRulesFilePath = forwardingRulesRemotePath
+ rulesRegex = forwardingRulesRegex
+ }
+ }
+
+ doTheJob(filePathToImport)
+ }
+
+ private fun doTheJob(
+ filesToImport: Array<*>
+ ) {
+
+ reentrantLock.lock()
+
+ if (!wakeLocksManager.isPowerWakeLockHeld) {
+ wakeLocksManager.managePowerWakelock(context, true)
+ powerLocked = true
+ }
+
+ try {
+
+ val fileToSave = when (importType) {
+ ImportType.LOCAL_RULES, ImportType.SINGLE_RULES -> localRulesFilePath
+ ImportType.REMOTE_RULES -> remoteRulesFilePath
+ }
+
+ if (filesToImport.isNotEmpty()) {
+ File(fileToSave).printWriter().use {
+ filesToImport.map { path ->
+ when (path) {
+ is String -> getFileNameFromPath(path)
+ is Uri -> getFileNameFromUri(path)
+ else -> throw IllegalArgumentException("ImportRulesManager unknown path type")
+ }
+ }.let { names ->
+ fileNames = names.joinToString()
+ when (importType) {
+ ImportType.LOCAL_RULES -> addFileHeader(it, names)
+
+ ImportType.REMOTE_RULES -> remoteRulesUrl?.let { url ->
+ addFileHeader(it, listOf(url))
+ }
+
+ ImportType.SINGLE_RULES -> Unit
+ }
+ }
+ mixFiles(it, filesToImport.toMutableList(), true)
+ }
+ }
+
+ val fileToAdd = when (importType) {
+ ImportType.LOCAL_RULES, ImportType.SINGLE_RULES -> remoteRulesFilePath
+ ImportType.REMOTE_RULES -> localRulesFilePath
+ }
+
+ val filesForFinalMixing = mutableListOf(
+ singleRulesFilePath, fileToSave, fileToAdd
+ )
+
+ File(rulesFilePath).printWriter().use {
+ //addDefaultLinesIfRequired()
+ mixFiles(it, filesForFinalMixing, false)
+ }
+
+ if (importedLinesCount > 0) {
+ sendImportFinishedBroadcast(
+ getRulesFileSize(),
+ importedLinesCount
+ )
+ } else {
+ sendImportFailedBroadcast(
+ "Imported zero rules"
+ )
+ }
+
+ sendTotalRulesBroadcast(totalLinesCount)
+
+ } catch (e: Exception) {
+ sendImportFailedBroadcast(
+ e.message ?: ""
+ )
+ loge("ImportRules doTheJob", e)
+ } finally {
+
+ if (powerLocked) {
+ wakeLocksManager.stopPowerWakelock()
+ }
+
+ restartDNSCryptIfRequired()
+
+ reentrantLock.unlock()
+ }
+
+ }
+
+ private fun mixFiles(
+ printWriter: PrintWriter,
+ filesToImport: MutableList,
+ countAsImportedLines: Boolean
+ ) {
+ try {
+
+ hashes.clear()
+
+ filesToImport.forEachIndexed { index, file ->
+
+ if (file is String) {
+ mixFilesWithPass(
+ index,
+ file,
+ filesToImport.size,
+ rulesRegex,
+ printWriter,
+ countAsImportedLines
+ )
+ } else if (file is Uri) {
+ mixFilesWithUri(
+ index,
+ file,
+ filesToImport.size,
+ rulesRegex,
+ printWriter,
+ countAsImportedLines
+ )
+ }
+ }
+
+ sendImportProgressBroadcast(
+ getRulesFileSize(),
+ importedLinesCount
+ )
+ sendTotalRulesBroadcast(totalLinesCount)
+ } catch (e: Exception) {
+ loge("ImportRules mixFiles", e)
+ }
+ }
+
+ private fun mixFilesWithPass(
+ index: Int,
+ file: String,
+ filesCount: Int,
+ rulesRegex: Regex,
+ printWriter: PrintWriter,
+ countAsImportedLines: Boolean
+ ) {
+
+ if (file.isNotEmpty()) {
+ val inputFile = File(file)
+
+ if (inputFile.isFile) {
+ try {
+ val blackListFileIsHost = if (DnsRuleType.BLACKLIST == rulesVariant) {
+ isInputFileFormatCorrect(inputFile, hostFileRegex)
+ } else {
+ false
+ }
+
+ val hashesNew = hashSetOf()
+
+ if (blackListFileIsHost || isInputFileFormatCorrect(inputFile, rulesRegex)) {
+ inputFile.bufferedReader().use { reader ->
+ mixFilesCommonPart(
+ printWriter,
+ reader,
+ rulesRegex,
+ index,
+ filesCount,
+ hashesNew,
+ blackListFileIsHost,
+ countAsImportedLines
+ )
+ }
+ }
+ } catch (e: Exception) {
+ loge("ImportRules mixFilesWithPass", e)
+ }
+ }
+ }
+ }
+
+ private fun mixFilesWithUri(
+ index: Int,
+ uri: Uri,
+ filesCount: Int,
+ rulesRegex: Regex,
+ printWriter: PrintWriter,
+ countAsImportedLines: Boolean
+ ) {
+ try {
+ val blackListFileIsHost = if (DnsRuleType.BLACKLIST == rulesVariant) {
+ isInputFileFormatCorrect(uri, hostFileRegex)
+ } else {
+ false
+ }
+
+ val hashesNew = hashSetOf()
+
+ if (blackListFileIsHost || isInputFileFormatCorrect(uri, rulesRegex)) {
+ contentResolver.openInputStream(uri)?.use { inputStream ->
+ BufferedReader(InputStreamReader(inputStream)).use { reader ->
+ mixFilesCommonPart(
+ printWriter,
+ reader,
+ rulesRegex,
+ index,
+ filesCount,
+ hashesNew,
+ blackListFileIsHost,
+ countAsImportedLines
+ )
+ }
+ }
+ }
+ } catch (e: Exception) {
+ loge("ImportRules mixFilesWithUri", e)
+ }
+ }
+
+ private fun mixFilesCommonPart(
+ printWriter: PrintWriter,
+ reader: BufferedReader,
+ rulesRegex: Regex,
+ index: Int,
+ filesCount: Int,
+ hashesNew: MutableSet,
+ blackListFileIsHost: Boolean,
+ countAsImportedLines: Boolean
+ ) {
+ var line = reader.readLine()?.trim()
+ while (line != null && !Thread.currentThread().isInterrupted) {
+ val lineReady = if (blackListFileIsHost) {
+ hostToBlackList(line)
+ } else {
+ cleanRule(line, rulesRegex)
+ }
+
+ if (lineReady.isNotEmpty() && (index == 0 || !hashes.contains(lineReady))) {
+
+ if (filesCount > 1 && index < filesCount - 1) {
+ hashesNew += lineReady
+ }
+
+ printWriter.println(lineReady)
+ if (countAsImportedLines) {
+ importedLinesCount++
+ } else {
+ totalLinesCount++
+ }
+ val currentTime = System.currentTimeMillis()
+ if (currentTime - savedTime > 300) {
+ if (countAsImportedLines) {
+ sendImportProgressBroadcast(
+ getRulesFileSize(),
+ importedLinesCount
+ )
+ } else {
+ sendTotalRulesBroadcast(totalLinesCount)
+ }
+ savedTime = currentTime
+ }
+ }
+ line = reader.readLine()?.trim()
+ }
+
+ hashes += hashesNew
+
+ if (countAsImportedLines) {
+ sendImportProgressBroadcast(
+ getRulesFileSize(),
+ importedLinesCount
+ )
+ } else {
+ sendTotalRulesBroadcast(totalLinesCount)
+ }
+ }
+
+ private fun cleanRule(line: String, regExp: Regex): String {
+
+ if (line.startsWith("#") || !line.matches(regExp)) {
+ return ""
+ }
+
+ return line
+ }
+
+ private fun hostToBlackList(line: String): String {
+ var output = ""
+
+ if (line.startsWith("#") || !line.matches(hostFileRegex)) {
+ return ""
+ }
+
+ val index = line.lastIndexOf(" ") + 1
+ if (index in 8 until line.length) {
+ output = line.substring(index)
+ }
+
+ if (excludeFromHost.contains(output)) {
+ return ""
+ }
+
+ return output
+ }
+
+ private fun addDefaultLinesIfRequired() {
+ if (DnsRuleType.CLOAKING == rulesVariant) {
+ val file = File(singleRulesFilePath)
+ if (!file.isFile) {
+ file.createNewFile()
+ }
+ if (file.length() == 0L) {
+ file.printWriter().use {
+ it.println(defaultCloakingRule)
+ }
+ }
+ } else if (DnsRuleType.FORWARDING == rulesVariant) {
+ val file = File(singleRulesFilePath)
+ if (!file.isFile) {
+ file.createNewFile()
+ }
+ if (file.length() == 0L) {
+ file.printWriter().use {
+ it.println(defaultForwardingRule)
+ }
+ }
+ }
+ }
+
+ private fun isInputFileFormatCorrect(file: File, regExp: Regex): Boolean {
+ file.bufferedReader().use {
+ try {
+ return isInputFileFormatCorrect(it, regExp)
+ } catch (e: Exception) {
+ loge("ImportRules isInputFileFormatCorrect", e)
+ }
+ }
+ return false
+ }
+
+ private fun isInputFileFormatCorrect(uri: Uri, regExp: Regex): Boolean {
+ contentResolver.openInputStream(uri)?.use { inputStream ->
+ try {
+ BufferedReader(InputStreamReader(inputStream)).use { reader ->
+ return isInputFileFormatCorrect(reader, regExp)
+ }
+ } catch (e: Exception) {
+ loge("ImportRules isInputFileFormatCorrect", e)
+ }
+ }
+ return false
+ }
+
+ private fun isInputFileFormatCorrect(reader: BufferedReader, regExp: Regex): Boolean {
+ var line: String? = reader.readLine()?.trim()
+ var index = 0
+ while (line != null) {
+
+ if (Thread.currentThread().isInterrupted) {
+ return false
+ }
+
+ if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) {
+ index++
+ if (line.matches(regExp)) {
+ return true
+ } else if (index > 100) {
+ return false
+ }
+ }
+
+ line = reader.readLine()?.trim()
+ }
+ return false
+ }
+
+ private fun restartDNSCryptIfRequired() {
+ if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) {
+ ModulesRestarter.restartDNSCrypt(context)
+ }
+ }
+
+ private fun getFileNameFromUri(uri: Uri): String {
+ val cursor = context.contentResolver.query(uri, null, null, null, null)
+ cursor?.moveToFirst()
+ val nameColumnIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME) ?: -1
+ val fileName = if (nameColumnIndex >= 0) {
+ cursor?.getString(nameColumnIndex) ?: ""
+ } else {
+ ""
+ }
+ cursor?.close()
+ return fileName
+ }
+
+ private fun getFileNameFromPath(path: String) =
+ path.substringAfterLast("/")
+
+ private fun getRulesFileSize(): Long {
+ val filePath = when (importType) {
+ ImportType.LOCAL_RULES -> localRulesFilePath
+ ImportType.REMOTE_RULES -> remoteRulesFilePath
+ ImportType.SINGLE_RULES -> singleRulesFilePath
+ }
+ return File(filePath).length()
+ }
+
+ private fun addFileHeader(
+ printWriter: PrintWriter,
+ names: List
+ ) = with(printWriter) {
+ val nameLine = "# ${names.joinToString().replace("#", "")} #"
+ println("#".repeat(nameLine.length))
+ println()
+ println(nameLine)
+ println()
+ println("#".repeat(nameLine.length))
+ println()
+ }
+
+ private fun sendImportProgressBroadcast(
+ size: Long,
+ count: Int
+ ) {
+ when (importType) {
+ ImportType.LOCAL_RULES -> {
+ Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateProgress(
+ name = fileNames,
+ size = size,
+ count = count
+ )
+ )
+ }
+ }
+
+ ImportType.REMOTE_RULES -> {
+ Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateProgress(
+ name = remoteRulesName ?: "",
+ url = remoteRulesUrl,
+ size = size,
+ count = count
+ )
+ )
+ }
+ }
+
+ ImportType.SINGLE_RULES -> null
+ }?.let {
+ localBroadcastManager.sendBroadcast(it)
+ }
+ }
+
+ private fun sendImportFinishedBroadcast(
+ size: Long,
+ count: Int
+ ) {
+ when (importType) {
+ ImportType.LOCAL_RULES -> {
+ Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateFinished(
+ name = fileNames,
+ size = size,
+ count = count
+ )
+ )
+ }
+ }
+
+ ImportType.REMOTE_RULES -> {
+ Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateFinished(
+ name = remoteRulesName ?: "",
+ url = remoteRulesUrl,
+ size = size,
+ count = count
+ )
+ )
+ }
+ }
+
+ ImportType.SINGLE_RULES -> null
+ }?.let {
+ localBroadcastManager.sendBroadcast(it)
+ }
+ }
+
+ private fun sendImportFailedBroadcast(
+ error: String
+ ) {
+ when (importType) {
+ ImportType.LOCAL_RULES -> {
+ Intent(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateFailure(
+ name = fileNames,
+ error = error
+ )
+ )
+ }
+ }
+
+ ImportType.REMOTE_RULES -> {
+ Intent(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress.UpdateFailure(
+ name = remoteRulesName ?: "",
+ url = remoteRulesUrl,
+ error = error
+ )
+ )
+ }
+ }
+
+ ImportType.SINGLE_RULES -> null
+ }?.let {
+ localBroadcastManager.sendBroadcast(it)
+ }
+ }
+
+ private fun sendTotalRulesBroadcast(count: Int) {
+ Intent(UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA,
+ count
+ )
+ }.also {
+ localBroadcastManager.sendBroadcast(it)
+ }
+ }
+
+ enum class ImportType {
+ REMOTE_RULES,
+ LOCAL_RULES,
+ SINGLE_RULES
+ }
+
+ interface DnsRulesRegex {
+ companion object {
+ val blackListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?,]+$")
+ val blacklistIPRulesRegex = Regex("^[0-9a-fA-F:.=*\\[\\]]+$")
+ val cloakingRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+[ \\t]+[a-zA-Z\\d-.=_*:]+$")
+ val forwardingRulesRegex =
+ Regex("^[a-zA-Z\\d-._]+[ \\t]+[0-9a-fA-F:.,\\[\\]]+$")
+ val whiteListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+$")
+ val hostFileRegex = Regex("^(?:0.0.0.0|127.0.0.1)[ \\t]+[a-zA-Z\\d-._]+$")
+ }
+ }
+
+ companion object {
+ const val UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION =
+ "pan.alexander.tordnscrypt.UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION"
+ const val UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION =
+ "pan.alexander.tordnscrypt.UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION"
+ const val UPDATE_DNS_RULES_PROGRESS_DATA =
+ "pan.alexander.tordnscrypt.UPDATE_DNS_RULES_PROGRESS_DATA"
+ const val UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION =
+ "pan.alexander.tordnscrypt.UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION"
+ const val UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA =
+ "pan.alexander.tordnscrypt.UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt
new file mode 100644
index 000000000..6ee0a1b31
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalDnsRulesWorker.kt
@@ -0,0 +1,128 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local
+
+import android.content.Context
+import android.net.Uri
+import androidx.work.CoroutineWorker
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runInterruptible
+import pan.alexander.tordnscrypt.App
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_PATH_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_TYPE_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.LOCAL_RULES_URI_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REFRESH_REMOTE_DNS_WHITELIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_WHITELIST_WORK
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import javax.inject.Inject
+import javax.inject.Named
+
+class UpdateLocalDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ init {
+ App.instance.daggerComponent.inject(this)
+ }
+
+ @Inject
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ lateinit var dispatcherIo: CoroutineDispatcher
+
+ override suspend fun doWork(): Result {
+ try {
+ val ruleType = inputData.getString(LOCAL_RULES_TYPE_ARG)?.let {
+ DnsRuleType.valueOf(it)
+ } ?: return Result.failure()
+ val filesWithPath = inputData.getStringArray(LOCAL_RULES_PATH_ARG)
+ val filesWithUri = inputData.getStringArray(LOCAL_RULES_URI_ARG)
+ val files = filesWithPath ?: (filesWithUri?.map { Uri.parse(it) }?.toTypedArray()
+ ?: return Result.failure())
+
+ while (isRemoteDnsRulesImportingInProgress(ruleType)
+ || isMixDnsRulesInProgress(ruleType)
+ ) {
+ delay(500)
+ }
+
+ importRulesFromFiles(files, ruleType)
+
+ return Result.success()
+ } catch (e: Exception) {
+ loge("UpdateLocalDnsRulesWorker doWork", e)
+ }
+ return Result.failure()
+ }
+
+ private suspend fun importRulesFromFiles(
+ files: Array<*>,
+ ruleType: DnsRuleType,
+ ) = runInterruptible(dispatcherIo) {
+ ImportRulesManager(
+ context = appContext,
+ rulesVariant = ruleType,
+ importType = ImportRulesManager.ImportType.LOCAL_RULES,
+ filePathToImport = files
+ ).run()
+ }
+
+ private fun isRemoteDnsRulesImportingInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getRemoteWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+
+ private fun getRemoteWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK
+ }
+
+ private fun isMixDnsRulesInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getMixWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+ private fun getMixWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK
+ }
+
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt
new file mode 100644
index 000000000..e13bbe000
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/local/UpdateLocalRulesWorkManager.kt
@@ -0,0 +1,105 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.local
+
+import android.content.Context
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import androidx.work.workDataOf
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class UpdateLocalRulesWorkManager @Inject constructor(
+ private val context: Context,
+) {
+
+ fun startImportDnsRules(ruleType: DnsRuleType, filesToImport: Array<*>) {
+
+ filesToImport.firstOrNull() ?: return
+
+ val constraints = Constraints.Builder()
+ .setRequiresStorageNotLow(true)
+ .build()
+
+ val files = if (filesToImport.first() is String) {
+ LOCAL_RULES_PATH_ARG to filesToImport.map { it.toString() }.toTypedArray()
+ } else {
+ LOCAL_RULES_URI_ARG to filesToImport.map { it.toString() }.toTypedArray()
+ }
+
+ val importRequest = OneTimeWorkRequestBuilder()
+ .setConstraints(constraints)
+ .setBackoffCriteria(
+ BackoffPolicy.EXPONENTIAL,
+ WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ .setInputData(
+ workDataOf(
+ LOCAL_RULES_TYPE_ARG to ruleType.name,
+ files
+ )
+ )
+ .build()
+
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(
+ getWorkName(ruleType),
+ ExistingWorkPolicy.REPLACE,
+ importRequest
+ )
+ }
+
+ fun stopImportDnsRules(type: DnsRuleType) {
+ WorkManager.getInstance(context)
+ .cancelUniqueWork(getWorkName(type))
+ }
+
+ private fun getWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK
+ }
+
+ companion object {
+ const val LOCAL_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_TYPE_ARG"
+ const val LOCAL_RULES_PATH_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_PATH_ARG"
+ const val LOCAL_RULES_URI_ARG = "pan.alexander.tordnscrypt.LOCAL_RULES_URI_ARG"
+
+ const val REFRESH_LOCAL_DNS_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_BLACKLIST_WORK"
+ const val REFRESH_LOCAL_DNS_WHITELIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_WHITELIST_WORK"
+ const val REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK"
+ const val REFRESH_LOCAL_DNS_FORWARDING_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_FORWARDING_WORK"
+ const val REFRESH_LOCAL_DNS_CLOAKING_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_LOCAL_DNS_CLOAKING_WORK"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt
new file mode 100644
index 000000000..ed35e87b1
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/receiver/DnsRulesReceiver.kt
@@ -0,0 +1,278 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DownloadRemoteRulesManager.Companion.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DownloadRemoteRulesManager.Companion.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_DNS_RULES_PROGRESS_DATA
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager.Companion.UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.DnsRulesDownloadProgress
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.DnsRulesUpdateProgress
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler.DnsRuleRecycleItem
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import java.util.Date
+import javax.inject.Inject
+
+class DnsRulesReceiver @Inject constructor(
+ context: Context
+) : BroadcastReceiver() {
+
+ var callback: Callback? = null
+
+ private var receiverRegistered = false
+
+ private val localBroadcastManager by lazy {
+ LocalBroadcastManager.getInstance(context)
+ }
+
+ fun registerReceiver() {
+ try {
+ receiverRegistered = true
+ localBroadcastManager.registerReceiver(
+ this,
+ IntentFilter(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION)
+ )
+ localBroadcastManager.registerReceiver(
+ this,
+ IntentFilter(UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION)
+ )
+ localBroadcastManager.registerReceiver(
+ this,
+ IntentFilter(UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION)
+ )
+ localBroadcastManager.registerReceiver(
+ this,
+ IntentFilter(UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION)
+ )
+ } catch (e: Exception) {
+ loge("DnsRulesReceiver registerReceiver", e)
+ }
+ }
+
+ fun unregisterReceiver() {
+ try {
+ if (receiverRegistered) {
+ receiverRegistered = false
+ localBroadcastManager.unregisterReceiver(this)
+ }
+ } catch (e: Exception) {
+ loge("DnsRulesReceiver unregisterReceiver", e)
+ }
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ when (intent?.action) {
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION -> downloadRemoteRulesProgress(intent)
+ UPDATE_REMOTE_DNS_RULES_PROGRESS_ACTION -> updateRemoteRulesProgress(intent)
+ UPDATE_LOCAL_DNS_RULES_PROGRESS_ACTION -> updateLocalRulesProgress(intent)
+ UPDATE_TOTAL_DNS_RULES_PROGRESS_ACTION -> updateTotalRulesProgress(intent)
+ }
+ }
+
+ @Suppress("deprecation")
+ private fun downloadRemoteRulesProgress(intent: Intent) {
+ val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesDownloadProgress::class.java
+ )
+ } else {
+ intent.getParcelableExtra(
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA
+ )
+ }
+ data ?: return
+ when (data) {
+ is DnsRulesDownloadProgress.DownloadProgress -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.url,
+ date = Date(),
+ count = 0,
+ size = data.size,
+ inProgress = true
+ )
+ )
+ }
+
+ is DnsRulesDownloadProgress.DownloadFinished -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.url,
+ date = Date(),
+ count = 0,
+ size = data.size,
+ inProgress = false
+ )
+ )
+ }
+
+ is DnsRulesDownloadProgress.DownloadFailure -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.error,
+ date = Date(),
+ count = 0,
+ size = 0,
+ inProgress = false,
+ fault = true
+ )
+ )
+ }
+ }
+ }
+
+ @Suppress("deprecation")
+ private fun updateRemoteRulesProgress(intent: Intent) {
+ val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress::class.java
+ )
+ } else {
+ intent.getParcelableExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA
+ )
+ }
+ data ?: return
+ when (data) {
+ is DnsRulesUpdateProgress.UpdateProgress -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.url ?: "",
+ date = Date(),
+ count = data.count,
+ size = data.size,
+ inProgress = true
+ )
+ )
+ }
+
+ is DnsRulesUpdateProgress.UpdateFinished -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.url ?: "",
+ date = Date(),
+ count = data.count,
+ size = data.size,
+ inProgress = false
+ )
+ )
+ callback?.onUpdateFinished()
+ }
+
+ is DnsRulesUpdateProgress.UpdateFailure -> {
+ callback?.onUpdateRemoteRules(
+ DnsRuleRecycleItem.DnsRemoteRule(
+ name = data.name,
+ url = data.url ?: "",
+ date = Date(),
+ count = 0,
+ size = 0,
+ inProgress = false,
+ fault = true
+ )
+ )
+ callback?.onUpdateFinished()
+ }
+ }
+ }
+
+ @Suppress("deprecation")
+ private fun updateLocalRulesProgress(intent: Intent) {
+ val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesUpdateProgress::class.java
+ )
+ } else {
+ intent.getParcelableExtra(
+ UPDATE_DNS_RULES_PROGRESS_DATA
+ )
+ }
+ data ?: return
+ when (data) {
+ is DnsRulesUpdateProgress.UpdateProgress -> {
+ callback?.onUpdateLocalRules(
+ DnsRuleRecycleItem.DnsLocalRule(
+ name = data.name,
+ date = Date(),
+ count = data.count,
+ size = data.size,
+ inProgress = true
+ )
+ )
+ }
+
+ is DnsRulesUpdateProgress.UpdateFinished -> {
+ callback?.onUpdateLocalRules(
+ DnsRuleRecycleItem.DnsLocalRule(
+ name = data.name,
+ date = Date(),
+ count = data.count,
+ size = data.size,
+ inProgress = false
+ )
+ )
+ callback?.onUpdateFinished()
+ }
+
+ is DnsRulesUpdateProgress.UpdateFailure -> {
+ callback?.onUpdateLocalRules(
+ DnsRuleRecycleItem.DnsLocalRule(
+ name = data.name,
+ date = Date(),
+ count = 0,
+ size = 0,
+ inProgress = false,
+ fault = true
+ )
+ )
+ callback?.onUpdateFinished()
+ }
+ }
+ }
+
+ private fun updateTotalRulesProgress(intent: Intent) {
+ val count = intent.getIntExtra(UPDATE_TOTAL_DNS_RULES_PROGRESS_DATA, 0)
+ callback?.onUpdateTotalRules(count)
+ }
+
+ interface Callback {
+ fun onUpdateRemoteRules(rules: DnsRuleRecycleItem.DnsRemoteRule)
+ fun onUpdateLocalRules(rules: DnsRuleRecycleItem.DnsLocalRule)
+ fun onUpdateTotalRules(count: Int)
+ fun onUpdateFinished()
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt
new file mode 100644
index 000000000..c7bd3544f
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRuleRecycleItem.kt
@@ -0,0 +1,59 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler
+
+import java.util.Date
+
+sealed class DnsRuleRecycleItem {
+ data class DnsRemoteRule(
+ val name: String,
+ val url: String,
+ val date: Date,
+ val count: Int,
+ val size: Long,
+ val inProgress: Boolean,
+ val fault: Boolean = false
+ ) : DnsRuleRecycleItem()
+
+ data object AddRemoteRulesButton : DnsRuleRecycleItem()
+
+ data class DnsLocalRule(
+ val name: String,
+ val date: Date,
+ val count: Int,
+ val size: Long,
+ val inProgress: Boolean,
+ val fault: Boolean = false
+ ) : DnsRuleRecycleItem()
+
+ data object AddLocalRulesButton : DnsRuleRecycleItem()
+
+ data class DnsSingleRule(
+ var rule: String,
+ val protected: Boolean,
+ val active: Boolean
+ ) : DnsRuleRecycleItem()
+
+ data object AddSingleRuleButton : DnsRuleRecycleItem()
+
+ data class DnsRuleComment(
+ val comment: String
+ ) : DnsRuleRecycleItem()
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt
new file mode 100644
index 000000000..09166d4da
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/recycler/DnsRulesRecyclerAdapter.kt
@@ -0,0 +1,498 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.recycler
+
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.NO_POSITION
+import pan.alexander.tordnscrypt.R
+import pan.alexander.tordnscrypt.databinding.ItemButtonBinding
+import pan.alexander.tordnscrypt.databinding.ItemDnsRuleBinding
+import pan.alexander.tordnscrypt.databinding.ItemRulesBinding
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager
+import pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_NO_CAPTURING
+import pan.alexander.tordnscrypt.utils.Utils
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import java.text.DateFormat
+import kotlin.math.max
+
+private const val DNS_REMOTE_RULE = 1
+private const val DNS_REMOTE_RULE_BUTTON = 2
+private const val DNS_LOCAL_RULE = 3
+private const val DNS_LOCAL_RULE_BUTTON = 4
+private const val DNS_SINGLE_RULE = 5
+private const val DNS_SINGLE_RULE_BUTTON = 6
+private const val DNS_RULE_COMMENT = 7
+
+class DnsRulesRecyclerAdapter(
+ private val onImportLocalRules: () -> Unit,
+ private val onLocalRulesDelete: () -> Unit,
+ private val onDeltaSingleRules: (Int) -> Unit,
+ private val onRemoteRulesAdd: () -> Unit,
+ private val onRemoteRulesDelete: () -> Unit,
+ private val onRemoteRulesRefresh: () -> Unit,
+) : RecyclerView.Adapter() {
+
+ var rulesType: DnsRuleType? = null
+ private val rules: MutableList = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return try {
+ when (viewType) {
+ DNS_REMOTE_RULE, DNS_LOCAL_RULE -> {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_dns_rule, parent, false)
+ DnsRulesViewHolder(itemView)
+ }
+
+ DNS_SINGLE_RULE -> {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_rules, parent, false)
+ DnsSingleRuleViewHolder(itemView)
+ }
+
+ DNS_REMOTE_RULE_BUTTON, DNS_LOCAL_RULE_BUTTON, DNS_SINGLE_RULE_BUTTON -> {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_button, parent, false)
+ DnsRuleButtonViewHolder(itemView)
+ }
+
+ else -> throw IllegalArgumentException("DnsRulesRecyclerAdapter unknown view type")
+ }
+
+ } catch (e: Exception) {
+ loge("DnsRulesRecyclerAdapter onCreateViewHolder", e)
+ throw e
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ when (holder.itemViewType) {
+ DNS_REMOTE_RULE, DNS_LOCAL_RULE -> (holder as DnsRulesViewHolder).bind(position)
+ DNS_SINGLE_RULE -> (holder as DnsSingleRuleViewHolder).bind(position)
+ DNS_REMOTE_RULE_BUTTON, DNS_LOCAL_RULE_BUTTON, DNS_SINGLE_RULE_BUTTON ->
+ (holder as DnsRuleButtonViewHolder).bind(position)
+
+ else -> throw IllegalArgumentException("DnsRulesRecyclerAdapter unknown view type")
+ }
+ }
+
+ override fun getItemCount(): Int = rules.size
+
+ override fun getItemViewType(position: Int): Int {
+ return when (rules[position]) {
+ is DnsRuleRecycleItem.DnsRemoteRule -> DNS_REMOTE_RULE
+ is DnsRuleRecycleItem.AddRemoteRulesButton -> DNS_REMOTE_RULE_BUTTON
+ is DnsRuleRecycleItem.DnsLocalRule -> DNS_LOCAL_RULE
+ is DnsRuleRecycleItem.AddLocalRulesButton -> DNS_LOCAL_RULE_BUTTON
+ is DnsRuleRecycleItem.DnsSingleRule -> DNS_SINGLE_RULE
+ is DnsRuleRecycleItem.AddSingleRuleButton -> DNS_SINGLE_RULE_BUTTON
+ is DnsRuleRecycleItem.DnsRuleComment -> DNS_RULE_COMMENT
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun updateRules(rules: List) {
+ this.rules.apply {
+ clear()
+ addAll(rules)
+ notifyDataSetChanged()
+ }
+ }
+
+ fun updateRemoteRules(remoteRule: DnsRuleRecycleItem.DnsRemoteRule) {
+ for (i in rules.indices) {
+ val rule = rules[i]
+ if (rule is DnsRuleRecycleItem.DnsRemoteRule) {
+ rules[i] = remoteRule
+ notifyItemChanged(i, Any())
+ break
+ } else if (rule is DnsRuleRecycleItem.AddRemoteRulesButton) {
+ rules.add(i, remoteRule)
+ notifyItemInserted(i)
+ break
+ }
+ }
+ }
+
+ fun updateLocalRules(localRule: DnsRuleRecycleItem.DnsLocalRule) {
+ for (i in rules.indices) {
+ val rule = rules[i]
+ if (rule is DnsRuleRecycleItem.DnsLocalRule) {
+ rules[i] = localRule
+ notifyItemChanged(i, Any())
+ break
+ } else if (rule is DnsRuleRecycleItem.AddLocalRulesButton) {
+ rules.add(i, localRule)
+ notifyItemInserted(i)
+ break
+ }
+ }
+ }
+
+ fun getRules(): List = rules
+
+ private inner class DnsRulesViewHolder(itemView: View) : BaseViewHolder(itemView) {
+
+ private val textColorGreen by lazy {
+ ContextCompat.getColor(itemView.context, R.color.colorGreen)
+ }
+
+ private val textColorSecondary by lazy {
+ ContextCompat.getColor(itemView.context, R.color.colorTextSecondary)
+ }
+
+ private val faultColorFault by lazy {
+ ContextCompat.getColor(itemView.context, R.color.colorAlert)
+ }
+
+ override fun bind(position: Int) {
+ when (val rule = rules[position]) {
+ is DnsRuleRecycleItem.DnsRemoteRule -> {
+ ItemDnsRuleBinding.bind(itemView).apply {
+ tvDnsRuleFileName.text = rule.name
+ tvDnsRuleUrl.visibility = VISIBLE
+ tvDnsRuleUrl.text = rule.url
+ if (rule.fault) {
+ tvDnsRuleUrl.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleUrl.setTextColor(textColorSecondary)
+ }
+ tvDnsRuleFileDate.text =
+ DateFormat.getDateInstance(DateFormat.SHORT).format(rule.date)
+ if (rule.fault) {
+ tvDnsRuleFileDate.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileDate.setTextColor(textColorGreen)
+ }
+ tvDnsRuleFileSize.text = Utils.formatFileSizeToReadableUnits(rule.size)
+ if (rule.size == 0L) {
+ tvDnsRuleFileSize.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileSize.setTextColor(textColorGreen)
+ }
+ tvDnsRuleFileQuantity.text = rule.count.toString()
+ if (rule.count == 0) {
+ tvDnsRuleFileQuantity.setTextColor(faultColorFault)
+ tvDnsRuleFileQuantityRules.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileQuantity.setTextColor(textColorGreen)
+ tvDnsRuleFileQuantityRules.setTextColor(textColorGreen)
+ }
+ btnDnsRuleFileDelete.setOnClickListener { deleteRemoteRules() }
+ btnDnsRuleFileRefresh.setOnClickListener { onRemoteRulesRefresh() }
+ if (rule.inProgress) {
+ pbDnsRule.visibility = VISIBLE
+ } else {
+ pbDnsRule.visibility = GONE
+ }
+ }
+ }
+
+ is DnsRuleRecycleItem.DnsLocalRule -> {
+ ItemDnsRuleBinding.bind(itemView).apply {
+ val name = rule.name.ifEmpty { "local-rules.txt" }
+ tvDnsRuleFileName.text = name
+ tvDnsRuleUrl.visibility = GONE
+ tvDnsRuleFileDate.text =
+ DateFormat.getDateInstance(DateFormat.SHORT).format(rule.date)
+ if (rule.fault) {
+ tvDnsRuleFileDate.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileDate.setTextColor(textColorGreen)
+ }
+ tvDnsRuleFileSize.text = Utils.formatFileSizeToReadableUnits(rule.size)
+ if (rule.size == 0L) {
+ tvDnsRuleFileSize.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileSize.setTextColor(textColorGreen)
+ }
+ tvDnsRuleFileQuantity.text = rule.count.toString()
+ if (rule.count == 0) {
+ tvDnsRuleFileQuantity.setTextColor(faultColorFault)
+ tvDnsRuleFileQuantityRules.setTextColor(faultColorFault)
+ } else {
+ tvDnsRuleFileQuantity.setTextColor(textColorGreen)
+ tvDnsRuleFileQuantityRules.setTextColor(textColorGreen)
+ }
+ btnDnsRuleFileDelete.setOnClickListener { deleteLocalRules() }
+ btnDnsRuleFileRefresh.visibility = GONE
+ if (rule.inProgress) {
+ pbDnsRule.visibility = VISIBLE
+ pbDnsRule.isIndeterminate = true
+ } else {
+ pbDnsRule.isIndeterminate = false
+ pbDnsRule.visibility = GONE
+ }
+ }
+ }
+
+ else -> Unit
+ }
+ }
+
+ private fun deleteRemoteRules() {
+ val adapterPosition = bindingAdapterPosition
+ if (adapterPosition != NO_POSITION) {
+ rules.removeAt(adapterPosition)
+ onRemoteRulesDelete()
+ notifyItemRemoved(adapterPosition)
+ }
+ }
+
+ private fun deleteLocalRules() {
+ val adapterPosition = bindingAdapterPosition
+ if (adapterPosition != NO_POSITION) {
+ rules.removeAt(adapterPosition)
+ onLocalRulesDelete()
+ notifyItemRemoved(adapterPosition)
+ }
+ }
+ }
+
+ private inner class DnsSingleRuleViewHolder(itemView: View) : BaseViewHolder(itemView),
+ View.OnClickListener {
+
+ val watcher = object : TextWatcher {
+ override fun beforeTextChanged(
+ s: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ }
+
+ override fun afterTextChanged(s: Editable?) {
+ val position = bindingAdapterPosition
+ if (s == null || position == NO_POSITION) {
+ return
+ }
+
+ editRule(position, s.toString())
+ }
+
+ }
+
+ override fun bind(position: Int) {
+ when (val rule = rules[position]) {
+ is DnsRuleRecycleItem.DnsSingleRule -> {
+ ItemRulesBinding.bind(itemView).apply {
+ etRule.setText(rule.rule, TextView.BufferType.EDITABLE)
+ etRule.isEnabled = rule.active
+ etRule.addTextChangedListener(watcher)
+ swRuleActive.isChecked = rule.active
+ swRuleActive.setOnClickListener(this@DnsSingleRuleViewHolder)
+ if (rule.protected) {
+ delBtnRules.visibility = GONE
+ } else {
+ delBtnRules.visibility = VISIBLE
+ delBtnRules.setOnClickListener(this@DnsSingleRuleViewHolder)
+ }
+ }
+ }
+
+ else -> Unit
+ }
+ }
+
+ override fun onClick(v: View?) {
+ val position = bindingAdapterPosition
+ if (position == NO_POSITION) {
+ return
+ }
+
+ when (v?.id) {
+ R.id.delBtnRules -> deleteRule(position)
+ R.id.swRuleActive -> toggleRule(position)
+ }
+ }
+
+ }
+
+ private fun addRule() {
+
+ for (rule in rules) {
+ if (rule is DnsRuleRecycleItem.DnsSingleRule && rule.rule.isBlank()) {
+ return
+ }
+ }
+
+ rules.add(
+ max(rules.size - 1, 0),
+ DnsRuleRecycleItem.DnsSingleRule(
+ rule = "",
+ protected = false,
+ active = true
+ )
+ )
+ onDeltaSingleRules(1)
+ notifyItemInserted(rules.size - 1)
+ }
+
+ private fun toggleRule(position: Int) {
+ val rule = rules[position]
+ if (rule is DnsRuleRecycleItem.DnsSingleRule) {
+ rules[position] = DnsRuleRecycleItem.DnsSingleRule(
+ rule = rule.rule,
+ protected = rule.protected,
+ active = !rule.active
+ )
+ onDeltaSingleRules(if (rule.active) -1 else 1)
+ notifyItemChanged(position)
+ }
+ }
+
+ private fun editRule(position: Int, text: String) {
+ val rule = rules[position]
+ if (rule is DnsRuleRecycleItem.DnsSingleRule) {
+ if (text.contains("\n")) {
+ text.split("\n").map {
+ it.trim()
+ }.map {
+ when (rulesType) {
+ DnsRuleType.BLACKLIST, DnsRuleType.WHITELIST ->
+ Utils.getDomainNameFromUrl(it)
+
+ DnsRuleType.IP_BLACKLIST -> prepareIPv6IfAny(it)
+
+ else -> it
+ }
+ }.filter {
+ it.isNotBlank() && it.removePrefix("#").matches(getRuleRegex())
+ }.takeIf {
+ it.isNotEmpty()
+ }?.also {
+ rules[position] = DnsRuleRecycleItem.DnsSingleRule(
+ rule = it.first().removePrefix("#"),
+ protected = false,
+ active = !it.first().startsWith("#")
+ )
+ notifyItemChanged(position)
+ }?.drop(1)?.forEachIndexed { index, s ->
+ rules.add(
+ position + index + 1,
+ DnsRuleRecycleItem.DnsSingleRule(
+ rule = s.removePrefix("#"),
+ protected = false,
+ active = !s.startsWith("#")
+ )
+ )
+ notifyItemInserted(position + index + 1)
+ }
+ } else {
+ val textPrepared = when (rulesType) {
+ DnsRuleType.BLACKLIST, DnsRuleType.WHITELIST -> Utils.getDomainNameFromUrl(text)
+ DnsRuleType.IP_BLACKLIST -> prepareIPv6IfAny(text)
+ else -> text
+ }
+ if (textPrepared.matches(getRuleRegex())) {
+ rule.rule = textPrepared
+ if (textPrepared != text) {
+ notifyItemChanged(position)
+ }
+ }
+ }
+ }
+ }
+
+ private fun prepareIPv6IfAny(line: String): String =
+ if (line.matches(Regex(IPv6_REGEX_NO_CAPTURING))) {
+ "[$line]"
+ } else {
+ line
+ }
+
+ private fun getRuleRegex() = when (rulesType) {
+ DnsRuleType.BLACKLIST -> ImportRulesManager.DnsRulesRegex.blackListHostRulesRegex
+ DnsRuleType.WHITELIST -> ImportRulesManager.DnsRulesRegex.whiteListHostRulesRegex
+ DnsRuleType.IP_BLACKLIST -> ImportRulesManager.DnsRulesRegex.blacklistIPRulesRegex
+ DnsRuleType.FORWARDING -> ImportRulesManager.DnsRulesRegex.forwardingRulesRegex
+ DnsRuleType.CLOAKING -> ImportRulesManager.DnsRulesRegex.cloakingRulesRegex
+ null -> throw IllegalArgumentException("DnsRulesRecyclerAdapter getRuleRegex rulesType null")
+ }
+
+ private fun deleteRule(position: Int) {
+ rules.removeAt(position)
+ onDeltaSingleRules(-1)
+ notifyItemRemoved(position)
+ }
+
+ private inner class DnsRuleButtonViewHolder(itemView: View) : BaseViewHolder(itemView) {
+
+ override fun bind(position: Int) {
+ when (rules[position]) {
+ is DnsRuleRecycleItem.AddRemoteRulesButton -> {
+ ItemButtonBinding.bind(itemView).apply {
+ if (rules.firstOrNull { it is DnsRuleRecycleItem.DnsRemoteRule } == null) {
+ btnAddRuleItem.setText(R.string.dns_rule_add_remote_list)
+ } else {
+ btnAddRuleItem.setText(R.string.dns_rule_replace_remote_list)
+ }
+ btnAddRuleItem.setOnClickListener {
+ onRemoteRulesAdd()
+ }
+ }
+ }
+
+ is DnsRuleRecycleItem.AddLocalRulesButton -> {
+ ItemButtonBinding.bind(itemView).apply {
+ if (rules.firstOrNull { it is DnsRuleRecycleItem.DnsLocalRule } == null) {
+ btnAddRuleItem.setText(R.string.dns_rule_add_local_list)
+ } else {
+ btnAddRuleItem.setText(R.string.dns_rule_replace_local_list)
+ }
+ btnAddRuleItem.setOnClickListener {
+ onImportLocalRules()
+ }
+ }
+ }
+
+ is DnsRuleRecycleItem.AddSingleRuleButton -> {
+ ItemButtonBinding.bind(itemView).apply {
+ btnAddRuleItem.setText(R.string.dns_rule_add_rule)
+ btnAddRuleItem.setOnClickListener {
+ addRule()
+ }
+ }
+ }
+
+ else -> Unit
+ }
+ }
+ }
+
+ private abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ abstract fun bind(position: Int)
+ }
+
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt
new file mode 100644
index 000000000..602f1eeb6
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DnsRulesDownloadProgress.kt
@@ -0,0 +1,46 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class DnsRulesDownloadProgress : Parcelable {
+
+ data class DownloadProgress(
+ val name: String,
+ val url: String,
+ val size: Long,
+ val progress: Int
+ ): DnsRulesDownloadProgress()
+
+ data class DownloadFinished(
+ val name: String,
+ val url: String,
+ val size: Long
+ ): DnsRulesDownloadProgress()
+
+ data class DownloadFailure(
+ val name: String,
+ val url: String,
+ val error: String
+ ): DnsRulesDownloadProgress()
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt
new file mode 100644
index 000000000..5fbd55c22
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/DownloadRemoteRulesManager.kt
@@ -0,0 +1,206 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.withContext
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.utils.Constants.TOR_BROWSER_USER_AGENT
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.utils.logger.Logger.logi
+import pan.alexander.tordnscrypt.utils.logger.Logger.logw
+import pan.alexander.tordnscrypt.utils.web.HttpsConnectionManager
+import java.io.File
+import java.io.IOException
+import javax.inject.Inject
+import javax.inject.Named
+
+private const val READ_TIMEOUT_SEC = 30
+private const val CONNECT_TIMEOUT_SEC = 30
+private const val ATTEMPTS_TO_DOWNLOAD = 5
+private const val TIME_TO_DOWNLOAD_MINUTES = 10
+private const val ATTEMPTS_TO_DOWNLOAD_WITHIN_TIME = 20
+private const val UPDATE_PROGRESS_INTERVAL_MSEC = 300
+
+class DownloadRemoteRulesManager @Inject constructor(
+ private val context: Context,
+ private val pathVars: PathVars,
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ private val dispatcherIo: CoroutineDispatcher,
+ private val httpsConnectionManager: HttpsConnectionManager
+) {
+
+ private val localBroadcastManager by lazy {
+ LocalBroadcastManager.getInstance(context)
+ }
+
+ suspend fun downloadRules(ruleName: String, url: String, fileName: String): File? =
+ withContext(dispatcherIo) {
+ var attempts = 0
+ val startTime = System.currentTimeMillis()
+ var outputFile: File? = null
+ var error = ""
+ try {
+ val path = "${pathVars.getCacheDirPath(context)}/$fileName"
+ val oldFile = File(path)
+ if (oldFile.isFile) {
+ oldFile.delete()
+ }
+ outputFile = File(path).apply {
+ createNewFile()
+ }
+ do {
+ attempts++
+ try {
+ outputFile = tryDownload(ruleName, url, path)
+ } catch (e: IOException) {
+ error = e.message ?: ""
+ logw(
+ "DownloadRulesManager failed to download file $url, attempt $attempts",
+ e
+ )
+ }
+ } while (
+ outputFile == null && isActive &&
+ (attempts < ATTEMPTS_TO_DOWNLOAD
+ || System.currentTimeMillis() - startTime < TIME_TO_DOWNLOAD_MINUTES * 60000
+ && attempts < ATTEMPTS_TO_DOWNLOAD_WITHIN_TIME)
+ )
+ } catch (e: Exception) {
+ error = e.message ?: ""
+ loge("DownloadRulesManager failed to download file $url", e)
+ }
+ if (outputFile != null && outputFile.length() > 0) {
+ sendDownloadFinishedBroadcast(ruleName, url, outputFile.length())
+ logi("Downloading $url was successful")
+ } else {
+ sendDownloadFailedBroadcast(ruleName, url, error)
+ }
+ return@withContext outputFile
+ }
+
+ private suspend fun tryDownload(ruleName: String, url: String, filePath: String): File? =
+ withContext(dispatcherIo) {
+ logi("Downloading DNSCrypt rules $url")
+
+ var range: Long = 0
+ val file = File(filePath)
+ if (file.isFile) {
+ range = file.length()
+ } else {
+ file.createNewFile()
+ }
+
+ val connection = httpsConnectionManager.getHttpsUrlConnection(url).apply {
+ connectTimeout = CONNECT_TIMEOUT_SEC * 1000
+ readTimeout = READ_TIMEOUT_SEC * 1000
+ setRequestProperty("User-Agent", TOR_BROWSER_USER_AGENT)
+ if (range != 0L) {
+ setRequestProperty("Range", "bytes=$range-")
+ }
+ }
+ val fileLength = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ connection.contentLengthLong + range
+ } else {
+ connection.getContentLength() + range
+ }
+ connection.inputStream.buffered().use { input ->
+ val data = ByteArray(1024)
+ file.outputStream().use { output ->
+ var time = System.currentTimeMillis()
+ var count = input.read(data)
+ while (count != -1 && isActive) {
+ range += count
+ val percent = (range * 100 / fileLength).toInt()
+ val currentTime = System.currentTimeMillis()
+ if (currentTime - time > UPDATE_PROGRESS_INTERVAL_MSEC) {
+ time = currentTime
+ sendUpdateProgressBroadcast(ruleName, url, range, percent)
+ }
+ output.write(data, 0, count)
+ count = input.read(data)
+ }
+ }
+ }
+ connection.disconnect()
+ return@withContext if (isActive) {
+ file
+ } else {
+ file.delete()
+ null
+ }
+ }
+
+ private fun sendUpdateProgressBroadcast(
+ name: String,
+ url: String,
+ size: Long,
+ progress: Int
+ ) {
+ val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesDownloadProgress.DownloadProgress(name, url, size, progress)
+ )
+ }
+ localBroadcastManager.sendBroadcast(intent)
+ }
+
+ private fun sendDownloadFinishedBroadcast(
+ name: String,
+ url: String,
+ size: Long
+ ) {
+ val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesDownloadProgress.DownloadFinished(name, url, size)
+ )
+ }
+ localBroadcastManager.sendBroadcast(intent)
+ }
+
+ private fun sendDownloadFailedBroadcast(
+ name: String,
+ url: String,
+ error: String
+ ) {
+ val intent = Intent(DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION).apply {
+ putExtra(
+ DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA,
+ DnsRulesDownloadProgress.DownloadFailure(name, url, error)
+ )
+ }
+ localBroadcastManager.sendBroadcast(intent)
+ }
+
+ companion object {
+ const val DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION =
+ "pan.alexander.tordnscrypt.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_ACTION"
+ const val DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA =
+ "pan.alexander.tordnscrypt.DOWNLOAD_REMOTE_DNS_RULES_PROGRESS_DATA"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt
new file mode 100644
index 000000000..63e1df998
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteDnsRulesWorker.kt
@@ -0,0 +1,147 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runInterruptible
+import pan.alexander.tordnscrypt.App
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.ImportRulesManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager.Companion.REFRESH_LOCAL_DNS_WHITELIST_WORK
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_NAME_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_TYPE_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager.Companion.REMOTE_RULES_URL_ARG
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_CLOAKING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_FORWARDING_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_IP_BLACKLIST_WORK
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager.Companion.MIX_DNS_WHITELIST_WORK
+import java.io.File
+import javax.inject.Inject
+import javax.inject.Named
+
+class UpdateRemoteDnsRulesWorker(private val appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ init {
+ App.instance.daggerComponent.inject(this)
+ }
+
+ @Inject
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ lateinit var dispatcherIo: CoroutineDispatcher
+
+ @Inject
+ lateinit var downloadRulesManager: DownloadRemoteRulesManager
+
+ override suspend fun doWork(): Result {
+ try {
+ val url = inputData.getString(REMOTE_RULES_URL_ARG) ?: return Result.failure()
+ val ruleType = inputData.getString(REMOTE_RULES_TYPE_ARG)?.let {
+ DnsRuleType.valueOf(it)
+ } ?: return Result.failure()
+ val ruleName = inputData.getString(REMOTE_RULES_NAME_ARG) ?: return Result.failure()
+ val fileName = when (ruleType) {
+ DnsRuleType.BLACKLIST -> "blacklist-remote.txt"
+ DnsRuleType.WHITELIST -> "whitelist-remote.txt"
+ DnsRuleType.IP_BLACKLIST -> "ip-blacklist-remote.txt"
+ DnsRuleType.FORWARDING -> "forwarding-rules-remote.txt"
+ DnsRuleType.CLOAKING -> "cloaking-rules-remote.txt"
+ }
+ val file = downloadRulesManager.downloadRules(ruleName, url, fileName)
+
+ while (isLocalDnsRulesImportingInProgress(ruleType)
+ || isMixDnsRulesInProgress(ruleType)
+ ) {
+ delay(500)
+ }
+
+ file?.let {
+ if (it.length() > 0) {
+ importRulesFromFile(it, ruleType, url, ruleName)
+ } else {
+ return Result.retry()
+ }
+ } ?: return Result.failure()
+ return Result.success()
+ } catch (e: Exception) {
+ loge("UpdateRemoteDnsRulesWorker doWork", e)
+ }
+ return Result.failure()
+ }
+
+ private suspend fun importRulesFromFile(
+ file: File,
+ ruleType: DnsRuleType,
+ url: String,
+ ruleName: String
+ ) = runInterruptible(dispatcherIo) {
+ ImportRulesManager(
+ context = appContext,
+ rulesVariant = ruleType,
+ remoteRulesUrl = url,
+ remoteRulesName = ruleName,
+ importType = ImportRulesManager.ImportType.REMOTE_RULES,
+ filePathToImport = arrayOf(file.path)
+ ).run()
+ }
+
+ private fun isLocalDnsRulesImportingInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getLocalWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+
+ private fun getLocalWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_LOCAL_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_LOCAL_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_LOCAL_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_LOCAL_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_LOCAL_DNS_CLOAKING_WORK
+ }
+
+ private fun isMixDnsRulesInProgress(type: DnsRuleType): Boolean =
+ WorkManager.getInstance(appContext)
+ .getWorkInfosForUniqueWork(getMixWorkName(type)).get()
+ .firstOrNull()?.state == WorkInfo.State.RUNNING
+
+ private fun getMixWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> MIX_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> MIX_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> MIX_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> MIX_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> MIX_DNS_CLOAKING_WORK
+ }
+
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt
new file mode 100644
index 000000000..2faf4892d
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_rules/remote/UpdateRemoteRulesWorkManager.kt
@@ -0,0 +1,193 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkRequest.Companion.DEFAULT_BACKOFF_DELAY_MILLIS
+import androidx.work.workDataOf
+import pan.alexander.tordnscrypt.di.SharedPreferencesModule
+import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.utils.Utils.getDomainNameFromUrl
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RULES_REFRESH_DELAY
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+
+private const val DEFAULT_DELAY_HOURS = 72
+
+class UpdateRemoteRulesWorkManager @Inject constructor(
+ private val context: Context,
+ @Named(SharedPreferencesModule.DEFAULT_PREFERENCES_NAME)
+ private val defaultPreferences: SharedPreferences,
+ private val preferences: PreferenceRepository
+) {
+
+ private val workManager by lazy { WorkManager.getInstance(context) }
+
+ fun startRefreshDnsRules(ruleName: String, ruleType: DnsRuleType) {
+
+ val interval = getInterval()
+ if (interval == 0L) {
+ return
+ }
+
+ val updateRequest =
+ PeriodicWorkRequestBuilder(interval, TimeUnit.HOURS)
+ .setConstraints(getConstraints())
+ .setBackoffCriteria(
+ BackoffPolicy.EXPONENTIAL,
+ DEFAULT_BACKOFF_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ .setInputData(
+ workDataOf(
+ REMOTE_RULES_TYPE_ARG to ruleType.name,
+ REMOTE_RULES_NAME_ARG to ruleName,
+ REMOTE_RULES_URL_ARG to getRuleUrl(ruleType)
+ )
+ )
+ .build()
+
+ workManager.enqueueUniquePeriodicWork(
+ getWorkName(ruleType),
+ ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
+ updateRequest
+ )
+ }
+
+ fun updateRefreshDnsRulesInterval(interval: Long) =
+ DnsRuleType.entries.forEach { type ->
+ val workInfos = workManager.getWorkInfosForUniqueWork(getWorkName(type))
+ workInfos.get().firstOrNull()?.let { workInfo ->
+ try {
+ if (interval == 0L) {
+ stopRefreshDnsRules(type)
+ } else {
+ updateExistingWorkInterval(type, workInfo, interval)
+ }
+ } catch (e: Exception) {
+ loge("UpdateRemoteRulesWorkManager updateRefreshDnsRulesInterval", e)
+ }
+ }
+ }
+
+ private fun updateExistingWorkInterval(
+ ruleType: DnsRuleType,
+ workInfo: WorkInfo,
+ interval: Long
+ ) {
+ val updateRequest =
+ PeriodicWorkRequestBuilder(interval, TimeUnit.HOURS)
+ .setConstraints(getConstraints())
+ .setId(workInfo.id)
+ .setBackoffCriteria(
+ BackoffPolicy.EXPONENTIAL,
+ DEFAULT_BACKOFF_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ .setInputData(
+ workDataOf(
+ REMOTE_RULES_TYPE_ARG to ruleType.name,
+ REMOTE_RULES_NAME_ARG to getDomainNameFromUrl(getRuleUrl(ruleType)),
+ REMOTE_RULES_URL_ARG to getRuleUrl(ruleType)
+ )
+ )
+ .build()
+
+ workManager.updateWork(updateRequest)
+ }
+
+ fun stopRefreshDnsRules(type: DnsRuleType) {
+
+ val interval = getInterval()
+ if (interval == 0L) {
+ return
+ }
+
+ workManager.cancelUniqueWork(getWorkName(type))
+ }
+
+ private fun getInterval(): Long = try {
+ val refreshPeriod = defaultPreferences.getString(
+ DNSCRYPT_RULES_REFRESH_DELAY,
+ DEFAULT_DELAY_HOURS.toString()
+ )
+ refreshPeriod?.toLong() ?: DEFAULT_DELAY_HOURS.toLong()
+ } catch (e: Exception) {
+ loge("UpdateDnsRulesManager getInterval", e)
+ DEFAULT_DELAY_HOURS.toLong()
+ }
+
+ private fun getConstraints() = Constraints.Builder()
+ .setRequiresBatteryNotLow(true)
+ .setRequiresStorageNotLow(true)
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+
+ private fun getWorkName(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> REFRESH_REMOTE_DNS_BLACKLIST_WORK
+ DnsRuleType.WHITELIST -> REFRESH_REMOTE_DNS_WHITELIST_WORK
+ DnsRuleType.IP_BLACKLIST -> REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK
+ DnsRuleType.FORWARDING -> REFRESH_REMOTE_DNS_FORWARDING_WORK
+ DnsRuleType.CLOAKING -> REFRESH_REMOTE_DNS_CLOAKING_WORK
+ }
+
+ private fun getRuleUrl(type: DnsRuleType) =
+ when (type) {
+ DnsRuleType.BLACKLIST -> preferences.getStringPreference(REMOTE_BLACKLIST_URL)
+ DnsRuleType.WHITELIST -> preferences.getStringPreference(REMOTE_WHITELIST_URL)
+ DnsRuleType.IP_BLACKLIST -> preferences.getStringPreference(REMOTE_IP_BLACKLIST_URL)
+ DnsRuleType.FORWARDING -> preferences.getStringPreference(REMOTE_FORWARDING_URL)
+ DnsRuleType.CLOAKING -> preferences.getStringPreference(REMOTE_CLOAKING_URL)
+ }
+
+ companion object {
+ const val REMOTE_RULES_URL_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_URL_ARG"
+ const val REMOTE_RULES_NAME_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_NAME_ARG"
+ const val REMOTE_RULES_TYPE_ARG = "pan.alexander.tordnscrypt.REMOTE_RULES_TYPE_ARG"
+
+ const val REFRESH_REMOTE_DNS_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_BLACKLIST_WORK"
+ const val REFRESH_REMOTE_DNS_WHITELIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_WHITELIST_WORK"
+ const val REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_IP_BLACKLIST_WORK"
+ const val REFRESH_REMOTE_DNS_FORWARDING_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_FORWARDING_WORK"
+ const val REFRESH_REMOTE_DNS_CLOAKING_WORK =
+ "pan.alexander.tordnscrypt.REFRESH_REMOTE_DNS_CLOAKING_WORK"
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt
deleted file mode 100644
index 32033b480..000000000
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/EraseRules.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- This file is part of InviZible Pro.
-
- InviZible Pro 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.
-
- InviZible Pro 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 InviZible Pro. If not, see .
-
- Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
- */
-
-package pan.alexander.tordnscrypt.settings.dnscrypt_settings
-
-import android.content.Context
-import androidx.fragment.app.FragmentManager
-import androidx.preference.PreferenceManager
-import pan.alexander.tordnscrypt.App
-import pan.alexander.tordnscrypt.R
-import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment
-import pan.alexander.tordnscrypt.modules.ModulesRestarter
-import pan.alexander.tordnscrypt.modules.ModulesStatus
-import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant
-import pan.alexander.tordnscrypt.utils.enums.ModuleState
-import pan.alexander.tordnscrypt.utils.logger.Logger.loge
-import java.io.File
-import java.lang.Exception
-
-class EraseRules(private val context: Context,
- private val fragmentManager: FragmentManager,
- private val dnsCryptRulesVariant: DNSCryptRulesVariant,
- private val remoteRulesLinkPreferenceTag: String) : Thread() {
-
- override fun run() {
- val pathVars = App.instance.daggerComponent.getPathVars().get()
-
- when(dnsCryptRulesVariant) {
-
- DNSCryptRulesVariant.BLACKLIST_HOSTS -> eraseRuleVariant(pathVars.dnsCryptBlackListPath,
- pathVars.dnsCryptLocalBlackListPath, pathVars.dnsCryptRemoteBlackListPath)
-
- DNSCryptRulesVariant.BLACKLIST_IPS -> eraseRuleVariant(pathVars.dnsCryptIPBlackListPath,
- pathVars.dnsCryptLocalIPBlackListPath, pathVars.dnsCryptRemoteIPBlackListPath)
-
- DNSCryptRulesVariant.WHITELIST_HOSTS -> eraseRuleVariant(pathVars.dnsCryptWhiteListPath,
- pathVars.dnsCryptLocalWhiteListPath, pathVars.dnsCryptRemoteWhiteListPath)
-
- DNSCryptRulesVariant.CLOAKING -> eraseRuleVariant(pathVars.dnsCryptCloakingRulesPath,
- pathVars.dnsCryptLocalCloakingRulesPath, pathVars.dnsCryptRemoteCloakingRulesPath)
-
- DNSCryptRulesVariant.FORWARDING -> eraseRuleVariant(pathVars.dnsCryptForwardingRulesPath,
- pathVars.dnsCryptLocalForwardingRulesPath, pathVars.dnsCryptRemoteForwardingRulesPath)
-
- DNSCryptRulesVariant.UNDEFINED -> return
-
- }
- }
-
- private fun eraseRuleVariant(rulesFilePath: String, localRulesFilePath: String, remoteRulesFilePath: String) {
- eraseFile(rulesFilePath)
- eraseFile(localRulesFilePath)
- eraseFile(remoteRulesFilePath)
- erasePreference()
- restartDNSCryptIfRequired()
- showFinalDialog()
- }
-
- private fun eraseFile(filePath: String) {
-
- var eraseText = ""
- if (dnsCryptRulesVariant == DNSCryptRulesVariant.CLOAKING) {
- eraseText = "*i2p 10.191.0.1"
- } else if (dnsCryptRulesVariant == DNSCryptRulesVariant.FORWARDING) {
- eraseText = "onion 127.0.0.1:" + App.instance.daggerComponent.getPathVars().get().torDNSPort
- }
-
- try {
- val file = File(filePath)
- if (file.isFile) {
- file.writeText(eraseText)
- }
- } catch (e: Exception) {
- loge("EraseRules", e)
- }
-
- }
-
- private fun erasePreference() {
- val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
- sharedPreferences.edit().putString(remoteRulesLinkPreferenceTag, "").apply()
- }
-
- private fun showFinalDialog() {
- val dialog = NotificationDialogFragment.newInstance(R.string.erase_dnscrypt_rules_dialog_message)
- dialog.show(fragmentManager, "EraseDialog")
- }
-
- private fun restartDNSCryptIfRequired() {
- if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) {
- ModulesRestarter.restartDNSCrypt(context)
- }
- }
-}
\ No newline at end of file
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt
deleted file mode 100644
index 650e3a34f..000000000
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/ImportRules.kt
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- This file is part of InviZible Pro.
-
- InviZible Pro 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.
-
- InviZible Pro 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 InviZible Pro. If not, see .
-
- Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
- */
-
-package pan.alexander.tordnscrypt.settings.dnscrypt_settings
-
-import android.content.Context
-import android.net.Uri
-import pan.alexander.tordnscrypt.App
-import pan.alexander.tordnscrypt.modules.ModulesRestarter
-import pan.alexander.tordnscrypt.modules.ModulesStatus
-import pan.alexander.tordnscrypt.settings.PathVars
-import pan.alexander.tordnscrypt.utils.Constants.META_ADDRESS
-import pan.alexander.tordnscrypt.utils.wakelock.WakeLocksManager
-import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant
-import pan.alexander.tordnscrypt.utils.enums.ModuleState
-import pan.alexander.tordnscrypt.utils.logger.Logger.loge
-import java.io.BufferedReader
-import java.io.File
-import java.io.InputStreamReader
-import java.io.PrintWriter
-import java.util.*
-import java.util.concurrent.locks.ReentrantLock
-import kotlin.collections.ArrayList
-
-private val blackListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?,]+$")
-private val blacklistIPRulesRegex = Regex("^[0-9a-fA-F:.=*\\[\\]]+$")
-private val cloakingRulesRegex = Regex("^[a-zA-Z\\d-.=_*]+[ \\t]+[a-zA-Z\\d-.=_*:]+$")
-private val forwardingRulesRegex =
- Regex("^[a-zA-Z\\d-._]+[ \\t]+[0-9a-fA-F:.,\\[\\]]+$")
-private val whiteListHostRulesRegex = Regex("^[a-zA-Z\\d-.=_*\\[\\]?]+$")
-private val hostFileRegex = Regex("^(?:0.0.0.0|127.0.0.1)[ \\t]+[a-zA-Z\\d-._]+$")
-private const val itpdRedirectAddress = "*i2p 10.191.0.1"
-private val excludeFromHost = listOf("localhost", "localhost.localdomain", "local", META_ADDRESS)
-private val reentrantLock = ReentrantLock()
-private val wakeLocksManager = WakeLocksManager.getInstance()
-
-class ImportRules(
- private val context: Context,
- private var rulesVariant: DNSCryptRulesVariant,
- private val localRules: Boolean,
- private val filePathToImport: Array<*>
-) : Thread() {
-
- private val pathVars: PathVars = App.instance.daggerComponent.getPathVars().get()
-
- private val blackListHostRulesPath = pathVars.dnsCryptBlackListPath
- private val blackListHostRulesLocalPath = pathVars.dnsCryptLocalBlackListPath
- private val blackListHostRulesRemotePath = pathVars.dnsCryptRemoteBlackListPath
-
- private val blackListIPRulesPath = pathVars.dnsCryptIPBlackListPath
- private val blackListIPRulesLocalPath = pathVars.dnsCryptLocalIPBlackListPath
- private val blackListIPRulesRemotePath = pathVars.dnsCryptRemoteIPBlackListPath
-
- private val whiteListHostRulesPath = pathVars.dnsCryptWhiteListPath
- private val whiteListHostRulesLocalPath = pathVars.dnsCryptLocalWhiteListPath
- private val whiteListHostRulesRemotePath = pathVars.dnsCryptRemoteWhiteListPath
-
- private val cloakingRulesPath = pathVars.dnsCryptCloakingRulesPath
- private val cloakingRulesLocalPath = pathVars.dnsCryptLocalCloakingRulesPath
- private val cloakingRulesRemotePath = pathVars.dnsCryptRemoteCloakingRulesPath
-
- private val forwardingRulesPath = pathVars.dnsCryptForwardingRulesPath
- private val forwardingRulesLocalPath = pathVars.dnsCryptLocalForwardingRulesPath
- private val forwardingRulesRemotePath = pathVars.dnsCryptRemoteForwardingRulesPath
-
- private var onDNSCryptRuleAddLineListener: OnDNSCryptRuleAddLineListener? = null
-
- private var powerLocked = false
-
- private var blackListFileIsHost = false
-
- private val contentResolver = context.applicationContext.contentResolver
-
- @Volatile
- private var linesCount = 0
- private var hash = 0
- private var hashes = IntArray(0)
- private var savedTime = System.currentTimeMillis()
-
- interface OnDNSCryptRuleAddLineListener {
- fun onDNSCryptRuleLinesAddingStarted(importThread: Thread)
- fun onDNSCryptRuleLineAdded(count: Int)
- fun onDNSCryptRuleLinesAddingFinished()
- }
-
- fun setOnDNSCryptRuleAddLineListener(onDNSCryptRuleAddLineListener: OnDNSCryptRuleAddLineListener) {
- this.onDNSCryptRuleAddLineListener = onDNSCryptRuleAddLineListener
- }
-
- override fun run() {
-
- when (rulesVariant) {
- DNSCryptRulesVariant.BLACKLIST_HOSTS -> doTheJob(
- blackListHostRulesPath,
- blackListHostRulesLocalPath,
- blackListHostRulesRemotePath,
- blackListHostRulesRegex,
- filePathToImport
- )
-
- DNSCryptRulesVariant.WHITELIST_HOSTS -> doTheJob(
- whiteListHostRulesPath,
- whiteListHostRulesLocalPath,
- whiteListHostRulesRemotePath,
- whiteListHostRulesRegex,
- filePathToImport
- )
-
- DNSCryptRulesVariant.BLACKLIST_IPS -> doTheJob(
- blackListIPRulesPath,
- blackListIPRulesLocalPath,
- blackListIPRulesRemotePath,
- blacklistIPRulesRegex,
- filePathToImport
- )
-
- DNSCryptRulesVariant.CLOAKING -> doTheJob(
- cloakingRulesPath,
- cloakingRulesLocalPath,
- cloakingRulesRemotePath,
- cloakingRulesRegex,
- filePathToImport
- )
-
- DNSCryptRulesVariant.FORWARDING -> doTheJob(
- forwardingRulesPath,
- forwardingRulesLocalPath,
- forwardingRulesRemotePath,
- forwardingRulesRegex,
- filePathToImport
- )
-
- DNSCryptRulesVariant.UNDEFINED -> return
- }
-
- }
-
- private fun doTheJob(
- rulesFilePath: String,
- localRulesFilePath: String,
- remoteRulesFilePath: String,
- rulesRegex: Regex,
- filesToImport: Array<*>
- ) {
-
-
- reentrantLock.lock()
-
- if (!wakeLocksManager.isPowerWakeLockHeld) {
- wakeLocksManager.managePowerWakelock(context, true)
- powerLocked = true
- }
-
- onDNSCryptRuleAddLineListener?.onDNSCryptRuleLinesAddingStarted(currentThread())
-
- try {
- if (filesToImport.isNotEmpty()) {
- File(rulesFilePath).printWriter().use {
- addDefaultLinesIfRequired(it)
- mixFiles(
- it,
- localRulesFilePath,
- remoteRulesFilePath,
- rulesRegex,
- filesToImport.toMutableList()
- )
- }
- }
-
- val fileToSave = if (localRules) {
- localRulesFilePath
- } else {
- remoteRulesFilePath
- }
-
- try {
- File(rulesFilePath).copyTo(File(fileToSave), true)
- } catch (e: Exception) {
- onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(0)
- loge("ImportRules doTheJob copy", e)
- }
-
- } catch (e: Exception) {
- loge("ImportRules doTheJob", e)
- } finally {
- onDNSCryptRuleAddLineListener?.onDNSCryptRuleLinesAddingFinished()
-
- if (powerLocked) {
- wakeLocksManager.stopPowerWakelock()
- }
-
- if (linesCount > 0) {
- restartDNSCryptIfRequired()
- }
-
- reentrantLock.unlock()
- }
-
- }
-
- private fun mixFiles(
- printWriter: PrintWriter,
- localRulesFilePath: String,
- remoteRulesFilePath: String,
- rulesRegex: Regex,
- filesToImport: MutableList
- ) = try {
-
- val fileToAdd: String = if (localRules) {
- remoteRulesFilePath
- } else {
- localRulesFilePath
- }
-
- val addFile = File(fileToAdd)
- if (addFile.isFile) {
- filesToImport.add(addFile)
- }
-
- val hashIsRequired = filesToImport.size > 1
-
- filesToImport.forEachIndexed { index, file ->
-
- if (file is String) {
- mixFilesWithPass(index, file, rulesRegex, hashIsRequired, printWriter)
- } else if (file is Uri) {
- mixFilesWithUri(index, file, rulesRegex, hashIsRequired, printWriter)
- }
- }
-
- onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(linesCount)
- } catch (e: Exception) {
- loge("ImportRules mixFiles", e)
- }
-
- private fun mixFilesWithPass(
- index: Int, file: String,
- rulesRegex: Regex, hashIsRequired: Boolean, printWriter: PrintWriter
- ) {
-
- if (file.isNotEmpty()) {
- val inputFile = File(file)
-
- if (inputFile.isFile) {
- try {
- if (DNSCryptRulesVariant.BLACKLIST_HOSTS == rulesVariant) {
- blackListFileIsHost = isInputFileFormatCorrect(inputFile, hostFileRegex)
- }
-
- val hashesNew = ArrayList()
-
- if (blackListFileIsHost || isInputFileFormatCorrect(inputFile, rulesRegex)) {
- inputFile.bufferedReader().use { reader ->
- mixFilesCommonPart(
- printWriter,
- reader,
- rulesRegex,
- hashIsRequired,
- index,
- hashesNew
- )
- }
- }
- } catch (e: Exception) {
- loge("ImportRules mixFilesWithPass", e)
- }
- }
- }
- }
-
- private fun mixFilesWithUri(
- index: Int, uri: Uri,
- rulesRegex: Regex, hashIsRequired: Boolean, printWriter: PrintWriter
- ) {
- try {
- if (DNSCryptRulesVariant.BLACKLIST_HOSTS == rulesVariant) {
- blackListFileIsHost = isInputFileFormatCorrect(uri, hostFileRegex)
- }
-
- val hashesNew = ArrayList()
-
- if (blackListFileIsHost || isInputFileFormatCorrect(uri, rulesRegex)) {
- contentResolver.openInputStream(uri)?.use { inputStream ->
- BufferedReader(InputStreamReader(inputStream)).use { reader ->
- mixFilesCommonPart(
- printWriter,
- reader,
- rulesRegex,
- hashIsRequired,
- index,
- hashesNew
- )
- }
- }
- }
- } catch (e: Exception) {
- loge("ImportRules mixFilesWithUri", e)
- }
- }
-
- private fun mixFilesCommonPart(
- printWriter: PrintWriter, reader: BufferedReader,
- rulesRegex: Regex, hashIsRequired: Boolean,
- index: Int, hashesNew: ArrayList
- ) {
- var line = reader.readLine()?.trim()
- while (line != null && !currentThread().isInterrupted) {
- val lineReady = if (blackListFileIsHost) {
- hostToBlackList(line)
- } else {
- cleanRule(line, rulesRegex)
- }
-
- if (hashIsRequired) {
- hash = lineReady.hashCode()
- }
-
- if (lineReady.isNotEmpty() && (!hashIsRequired || index < 1 || Arrays.binarySearch(
- hashes,
- hash
- ) < 0)
- ) {
-
- if (hashIsRequired) {
- hashesNew += hash
- }
-
- printWriter.println(lineReady)
- linesCount++
- if (System.currentTimeMillis() - savedTime > 500) {
- onDNSCryptRuleAddLineListener?.onDNSCryptRuleLineAdded(linesCount)
- savedTime = System.currentTimeMillis()
- }
- }
- line = reader.readLine()?.trim()
- }
-
- val arrNew = IntArray(hashes.size + hashesNew.size)
- System.arraycopy(hashes, 0, arrNew, 0, hashes.size)
- System.arraycopy(hashesNew.toIntArray(), 0, arrNew, hashes.size, hashesNew.size)
- hashes = arrNew
- hashes.sort()
- }
-
- private fun cleanRule(line: String, regExp: Regex): String {
-
- if (line.startsWith("#") || !line.matches(regExp)) {
- return ""
- }
-
- return line
- }
-
- private fun hostToBlackList(line: String): String {
- var output = ""
-
- if (line.startsWith("#") || !line.matches(hostFileRegex)) {
- return ""
- }
-
- val index = line.lastIndexOf(" ") + 1
- if (index in 8 until line.length) {
- output = line.substring(index)
- }
-
- if (excludeFromHost.contains(output)) {
- return ""
- }
-
- return output
- }
-
- private fun addDefaultLinesIfRequired(printWriter: PrintWriter) {
- if (DNSCryptRulesVariant.CLOAKING == rulesVariant) {
- printWriter.println(itpdRedirectAddress)
- } else if (DNSCryptRulesVariant.FORWARDING == rulesVariant) {
- printWriter.println(
- "onion 127.0.0.1:" + App.instance.daggerComponent.getPathVars().get().torDNSPort
- )
- }
- }
-
- private fun isInputFileFormatCorrect(file: File, regExp: Regex): Boolean {
- file.bufferedReader().use {
- var line = it.readLine()?.trim()
- while (line != null) {
-
- if (currentThread().isInterrupted) {
- return false
- }
-
- if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) {
- return line.matches(regExp)
- }
-
- line = it.readLine()?.trim()
- }
- }
- return false
- }
-
- private fun isInputFileFormatCorrect(uri: Uri, regExp: Regex): Boolean {
- contentResolver.openInputStream(uri)?.use { inputStream ->
- BufferedReader(InputStreamReader(inputStream)).use { reader ->
- var line: String? = reader.readLine().trim()
- var index = 0
- while (line != null) {
-
- if (currentThread().isInterrupted) {
- return false
- }
-
- if (line.isNotEmpty() && !line.contains("#") && !line.contains("!")) {
- index++
- if (line.matches(regExp)) {
- return true
- } else if (index > 100) {
- return false
- }
- }
-
- line = reader.readLine().trim()
- }
- }
- }
-
- return false
- }
-
- private fun restartDNSCryptIfRequired() {
- if (ModulesStatus.getInstance().dnsCryptState == ModuleState.RUNNING) {
- ModulesRestarter.restartDNSCrypt(context)
- }
- }
-}
\ No newline at end of file
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java
index 593fb9866..3d3ba94c4 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/PreferencesDNSFragment.java
@@ -21,27 +21,19 @@
import android.app.Activity;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
+import android.os.Handler;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
-import com.github.angads25.filepicker.model.DialogConfigs;
-import com.github.angads25.filepicker.model.DialogProperties;
-import com.github.angads25.filepicker.view.FilePickerDialog;
-
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -51,20 +43,20 @@
import dagger.Lazy;
import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.R;
+import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment;
import pan.alexander.tordnscrypt.settings.SettingsActivity;
-import pan.alexander.tordnscrypt.dialogs.progressDialogs.ImportRulesDialog;
import pan.alexander.tordnscrypt.modules.ModulesAux;
import pan.alexander.tordnscrypt.modules.ModulesRestarter;
import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.settings.ConfigEditorFragment;
import pan.alexander.tordnscrypt.settings.PathVars;
-import pan.alexander.tordnscrypt.utils.Utils;
-import pan.alexander.tordnscrypt.utils.enums.DNSCryptRulesVariant;
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType;
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager;
+import pan.alexander.tordnscrypt.utils.enums.ModuleState;
import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor;
import pan.alexander.tordnscrypt.utils.filemanager.FileManager;
import pan.alexander.tordnscrypt.vpn.service.VpnBuilder;
-import static android.provider.DocumentsContract.EXTRA_INITIAL_URI;
import static pan.alexander.tordnscrypt.assistance.AccelerateDevelop.accelerated;
import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
import static pan.alexander.tordnscrypt.utils.Constants.DNSCRYPT_RELAYS_SOURCE_IPV6;
@@ -85,6 +77,9 @@
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_LISTEN_PORT;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_NETPROBE_ADDRESS;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_OUTBOUND_PROXY;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RELAYS_REFRESH_DELAY;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_RULES_REFRESH_DELAY;
+import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DNSCRYPT_SERVERS_REFRESH_DELAY;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.HTTP3_QUIC;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.IGNORE_SYSTEM_DNS;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
@@ -95,13 +90,7 @@
public class PreferencesDNSFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceChangeListener,
- Preference.OnPreferenceClickListener {
-
- public static final int PICK_BLACKLIST_HOSTS = 1001;
- public static final int PICK_WHITELIST_HOSTS = 1002;
- public static final int PICK_BLACKLIST_IPS = 1003;
- public static final int PICK_FORWARDING = 1004;
- public static final int PICK_CLOAKING = 1005;
+ Preference.OnPreferenceClickListener, RulesEraser.OnRulesErased {
@Inject
public Lazy pathVars;
@@ -110,6 +99,12 @@ public class PreferencesDNSFragment extends PreferenceFragmentCompat
@Inject
@Named(DEFAULT_PREFERENCES_NAME)
public SharedPreferences defaultPreferences;
+ @Inject
+ public Lazy handler;
+ @Inject
+ public Lazy rulesEraser;
+ @Inject
+ public Lazy updateRemoteRulesWorkManager;
private ArrayList key_toml;
private ArrayList val_toml;
@@ -117,7 +112,6 @@ public class PreferencesDNSFragment extends PreferenceFragmentCompat
private ArrayList val_toml_orig;
private String appDataDir;
private boolean isChanged = false;
- private boolean rootDirAccessible;
@SuppressWarnings("deprecation")
@Override
@@ -128,8 +122,6 @@ public void onCreate(Bundle savedInstanceState) {
addPreferencesFromResource(R.xml.preferences_dnscrypt);
- checkRootDirAccessible();
-
if (pathVars.get().getAppVersion().endsWith("p")) {
removePreferencesWithGPVersion();
}
@@ -155,9 +147,10 @@ public void onCreate(Bundle savedInstanceState) {
preferences.add(findPreference("ignored_qtypes"));
preferences.add(findPreference("Enable Suspicious logging"));
preferences.add(findPreference("Sources"));
- preferences.add(findPreference("refresh_delay"));
+ preferences.add(findPreference(DNSCRYPT_SERVERS_REFRESH_DELAY));
+ preferences.add(findPreference(DNSCRYPT_RULES_REFRESH_DELAY));
preferences.add(findPreference("Relays"));
- preferences.add(findPreference("refresh_delay_relays"));
+ preferences.add(findPreference(DNSCRYPT_RELAYS_REFRESH_DELAY));
preferences.add(findPreference("block_unqualified"));
preferences.add(findPreference("block_undelegated"));
preferences.add(findPreference(DNSCRYPT_BLOCK_IPv6));
@@ -185,7 +178,7 @@ public void onCreate(Bundle savedInstanceState) {
}
if (ModulesStatus.getInstance().isUseModulesWithRoot()) {
- removeImportErasePrefs();
+ removeErasePrefs();
} else {
registerImportErasePrefs();
}
@@ -194,11 +187,6 @@ public void onCreate(Bundle savedInstanceState) {
private void registerImportErasePrefs() {
ArrayList preferences = new ArrayList<>();
- preferences.add(findPreference("local_blacklist"));
- preferences.add(findPreference("local_whitelist"));
- preferences.add(findPreference("local_ipblacklist"));
- preferences.add(findPreference("local_forwarding_rules"));
- preferences.add(findPreference("local_cloaking_rules"));
preferences.add(findPreference("erase_blacklist"));
preferences.add(findPreference("erase_whitelist"));
preferences.add(findPreference("erase_ipblacklist"));
@@ -214,44 +202,34 @@ private void registerImportErasePrefs() {
}
}
- private void removeImportErasePrefs() {
+ private void removeErasePrefs() {
PreferenceCategory forwarding = findPreference("pref_dnscrypt_forwarding_rules");
- Preference local_forwarding_rules = findPreference("local_forwarding_rules");
Preference erase_forwarding_rules = findPreference("erase_forwarding_rules");
- if (forwarding != null && local_forwarding_rules != null && erase_forwarding_rules != null) {
- forwarding.removePreference(local_forwarding_rules);
+ if (forwarding != null && erase_forwarding_rules != null) {
forwarding.removePreference(erase_forwarding_rules);
}
PreferenceCategory cloaking = findPreference("pref_dnscrypt_cloaking_rules");
- Preference local_cloaking_rules = findPreference("local_cloaking_rules");
Preference erase_cloaking_rules = findPreference("erase_cloaking_rules");
- if (cloaking != null && local_cloaking_rules != null && erase_cloaking_rules != null) {
- cloaking.removePreference(local_cloaking_rules);
+ if (cloaking != null && erase_cloaking_rules != null) {
cloaking.removePreference(erase_cloaking_rules);
}
PreferenceCategory blacklist = findPreference("pref_dnscrypt_blacklist");
- Preference local_blacklist = findPreference("local_blacklist");
Preference erase_blacklist = findPreference("erase_blacklist");
- if (blacklist != null && local_blacklist != null && erase_blacklist != null) {
- blacklist.removePreference(local_blacklist);
+ if (blacklist != null && erase_blacklist != null) {
blacklist.removePreference(erase_blacklist);
}
PreferenceCategory ipblacklist = findPreference("pref_dnscrypt_ipblacklist");
- Preference local_ipblacklist = findPreference("local_ipblacklist");
Preference erase_ipblacklist = findPreference("erase_ipblacklist");
- if (ipblacklist != null && local_ipblacklist != null && erase_ipblacklist != null) {
- ipblacklist.removePreference(local_ipblacklist);
+ if (ipblacklist != null && erase_ipblacklist != null) {
ipblacklist.removePreference(erase_ipblacklist);
}
PreferenceCategory whitelist = findPreference("pref_dnscrypt_whitelist");
- Preference local_whitelist = findPreference("local_whitelist");
Preference erase_whitelist = findPreference("erase_whitelist");
- if (whitelist != null && local_whitelist != null && erase_whitelist != null) {
- whitelist.removePreference(local_whitelist);
+ if (whitelist != null && erase_whitelist != null) {
whitelist.removePreference(erase_whitelist);
}
}
@@ -402,16 +380,22 @@ public boolean onPreferenceChange(@NonNull Preference preference, Object newValu
}
val_toml.set(key_toml.lastIndexOf("urls"), newValue.toString());
return true;
- } else if (Objects.equals(preference.getKey(), "refresh_delay")) {
+ } else if (Objects.equals(preference.getKey(), DNSCRYPT_SERVERS_REFRESH_DELAY)) {
if (!newValue.toString().matches("\\d+")) {
return false;
}
- } else if (Objects.equals(preference.getKey(), "refresh_delay_relays")) {
+ } else if (Objects.equals(preference.getKey(), DNSCRYPT_RELAYS_REFRESH_DELAY)) {
if (!newValue.toString().matches("\\d+")) {
return false;
}
val_toml.set(key_toml.lastIndexOf("refresh_delay"), newValue.toString());
return true;
+ } else if (Objects.equals(preference.getKey(), DNSCRYPT_RULES_REFRESH_DELAY)) {
+ if (!newValue.toString().matches("\\d+")) {
+ return false;
+ }
+ refreshDnsCryptRulesUpdateInterval(Long.parseLong(newValue.toString()));
+ return true;
} else if (Objects.equals(preference.getKey(), DNSCRYPT_OUTBOUND_PROXY)) {
if (Boolean.parseBoolean(newValue.toString())
&& key_toml.contains("#proxy") && key_toml.contains("force_tcp")) {
@@ -558,6 +542,10 @@ public boolean onPreferenceChange(@NonNull Preference preference, Object newValu
return false;
}
+ private void refreshDnsCryptRulesUpdateInterval(long delay) {
+ updateRemoteRulesWorkManager.get().updateRefreshDnsRulesInterval(delay);
+ }
+
private List getDNSCryptBootstrapResolvers(String newValue) {
List resolvers = new ArrayList<>();
for (String resolver : newValue.split(", ?")) {
@@ -746,78 +734,23 @@ public boolean onPreferenceClick(@NonNull Preference preference) {
return true;
} else if (Objects.equals(preference.getKey().trim(), "erase_blacklist")) {
showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_blacklist, () ->
- eraseRules(activity, DNSCryptRulesVariant.BLACKLIST_HOSTS, "remote_blacklist"));
+ eraseRules(DnsRuleType.BLACKLIST));
return true;
} else if (Objects.equals(preference.getKey().trim(), "erase_whitelist")) {
showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_whitelist, () ->
- eraseRules(activity, DNSCryptRulesVariant.WHITELIST_HOSTS, "remote_whitelist"));
+ eraseRules(DnsRuleType.WHITELIST));
return true;
} else if (Objects.equals(preference.getKey().trim(), "erase_ipblacklist")) {
showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_ipblacklist, () ->
- eraseRules(activity, DNSCryptRulesVariant.BLACKLIST_IPS, "remote_ipblacklist"));
+ eraseRules(DnsRuleType.IP_BLACKLIST));
return true;
} else if (Objects.equals(preference.getKey().trim(), "erase_forwarding_rules")) {
showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_forwarding_rules, () ->
- eraseRules(activity, DNSCryptRulesVariant.FORWARDING, "remote_forwarding_rules"));
+ eraseRules(DnsRuleType.FORWARDING));
return true;
} else if (Objects.equals(preference.getKey().trim(), "erase_cloaking_rules")) {
showAreYouSureDialog(activity, R.string.pref_dnscrypt_erase_cloaking_rules, () ->
- eraseRules(activity, DNSCryptRulesVariant.CLOAKING, "remote_cloaking_rules"));
- return true;
- } else if (Objects.equals(preference.getKey().trim(), "local_blacklist")) {
-
- if (rootDirAccessible) {
- FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity));
- dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.BLACKLIST_HOSTS, files));
- dialog.show();
- } else {
- openFileWithSAF(activity, PICK_BLACKLIST_HOSTS);
- }
-
- return true;
- } else if (Objects.equals(preference.getKey().trim(), "local_whitelist")) {
-
- if (rootDirAccessible) {
- FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity));
- dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.WHITELIST_HOSTS, files));
- dialog.show();
- } else {
- openFileWithSAF(activity, PICK_WHITELIST_HOSTS);
- }
-
- return true;
- } else if (Objects.equals(preference.getKey().trim(), "local_ipblacklist")) {
-
- if (rootDirAccessible) {
- FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity));
- dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.BLACKLIST_IPS, files));
- dialog.show();
- } else {
- openFileWithSAF(activity, PICK_BLACKLIST_IPS);
- }
-
- return true;
- } else if (Objects.equals(preference.getKey().trim(), "local_forwarding_rules")) {
-
- if (rootDirAccessible) {
- FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity));
- dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.FORWARDING, files));
- dialog.show();
- } else {
- openFileWithSAF(activity, PICK_FORWARDING);
- }
-
- return true;
- } else if (Objects.equals(preference.getKey().trim(), "local_cloaking_rules")) {
-
- if (rootDirAccessible) {
- FilePickerDialog dialog = new FilePickerDialog(activity, getFilePickerProperties(activity));
- dialog.setDialogSelectionListener(files -> importRules(activity, DNSCryptRulesVariant.CLOAKING, files));
- dialog.show();
- } else {
- openFileWithSAF(activity, PICK_CLOAKING);
- }
-
+ eraseRules(DnsRuleType.CLOAKING));
return true;
} else if ("cleanDNSCryptFolder".equals(preference.getKey().trim())) {
cleanModuleFolder(activity);
@@ -826,28 +759,36 @@ public boolean onPreferenceClick(@NonNull Preference preference) {
return false;
}
- public void importRules(Context context, DNSCryptRulesVariant dnsCryptRulesVariant, Object[] files) {
+ private void eraseRules(DnsRuleType ruleType) {
+ RulesEraser eraser = rulesEraser.get();
+ eraser.setCallback(this);
+ executor.submit("PreferencesDNSFragment eraseRules", () -> {
+ eraser.eraseRules(ruleType);
+ return null;
+ });
+ }
- if (context == null) {
- return;
- }
+ @Override
+ public void onRulesEraseFinished() {
+ showRulesErasedDialog();
+ restartDnsCryptIfNeeded();
+ }
- ImportRules importRules = new ImportRules(context, dnsCryptRulesVariant,
- true, files);
- ImportRulesDialog importRulesDialog = ImportRulesDialog.newInstance();
- importRules.setOnDNSCryptRuleAddLineListener(importRulesDialog);
- importRulesDialog.show(getParentFragmentManager(), "ImportRulesDialog");
- importRules.start();
+ private void showRulesErasedDialog() {
+ DialogFragment dialog = NotificationDialogFragment.newInstance(R.string.erase_dnscrypt_rules_dialog_message);
+ if (handler != null) {
+ handler.get().post(() -> dialog.show(getParentFragmentManager(), "EraseDialog"));
+ }
}
- private void eraseRules(Context context, DNSCryptRulesVariant dnsCryptRulesVariant, String remoteRulesLinkPreferenceTag) {
+ private void restartDnsCryptIfNeeded() {
+ Context context = getContext();
if (context == null) {
return;
}
-
- EraseRules eraseRules = new EraseRules(context, getParentFragmentManager(),
- dnsCryptRulesVariant, remoteRulesLinkPreferenceTag);
- eraseRules.start();
+ if (ModulesStatus.getInstance().getDnsCryptState() == ModuleState.RUNNING) {
+ ModulesRestarter.restartDNSCrypt(context);
+ }
}
private void showAreYouSureDialog(Activity activity, int title, Runnable action) {
@@ -859,29 +800,6 @@ private void showAreYouSureDialog(Activity activity, int title, Runnable action)
builder.show();
}
- private void openFileWithSAF(Activity activity, int fileType) {
- if (activity == null) {
- return;
- }
-
- Uri uri = Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
-
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-
- if (uri != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- intent.putExtra(EXTRA_INITIAL_URI, uri);
- }
-
- PackageManager packageManager = activity.getPackageManager();
- if (packageManager != null && intent.resolveActivity(packageManager) != null) {
- activity.startActivityForResult(intent, fileType);
- }
-
- }
-
private void removePreferencesWithGPVersion() {
PreferenceScreen dnscryptSettings = findPreference("dnscrypt_settings");
@@ -891,6 +809,7 @@ private void removePreferencesWithGPVersion() {
categories.add(findPreference("pref_dnscrypt_blacklist"));
categories.add(findPreference("pref_dnscrypt_ipblacklist"));
categories.add(findPreference("pref_dnscrypt_whitelist"));
+ categories.add(findPreference("pref_dnscrypt_refresh_rules"));
for (PreferenceCategory category : categories) {
if (dnscryptSettings != null && category != null) {
@@ -934,33 +853,6 @@ private void removePreferencesWithGPVersion() {
}
}
- private void checkRootDirAccessible() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- executor.submit(
- "PreferencesDNSFragment checkRootDirAccessible",
- () -> {
- rootDirAccessible = Utils.INSTANCE.isLogsDirAccessible();
- return null;
- }
- );
- }
- }
-
- private DialogProperties getFilePickerProperties(Context context) {
-
- String cacheDirPath = pathVars.get().getCacheDirPath(context);
-
- DialogProperties properties = new DialogProperties();
- properties.selection_mode = DialogConfigs.MULTI_MODE;
- properties.selection_type = DialogConfigs.FILE_SELECT;
- properties.root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- properties.error_dir = new File(cacheDirPath);
- properties.offset = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- properties.extensions = new String[]{"txt"};
-
- return properties;
- }
-
private void cleanModuleFolder(Context context) {
if (ModulesStatus.getInstance().getDnsCryptState() != STOPPED) {
Toast.makeText(context, R.string.btnDNSCryptStop, Toast.LENGTH_SHORT).show();
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt
new file mode 100644
index 000000000..3df596264
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/dnscrypt_settings/RulesEraser.kt
@@ -0,0 +1,144 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.dnscrypt_settings
+
+import androidx.annotation.WorkerThread
+import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
+import pan.alexander.tordnscrypt.domain.dns_rules.DnsRuleType
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.existing.RemixExistingRulesWorkManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.local.UpdateLocalRulesWorkManager
+import pan.alexander.tordnscrypt.settings.dnscrypt_rules.remote.UpdateRemoteRulesWorkManager
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_CLOAKING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_FORWARDING_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_IP_BLACKLIST_URL
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REMOTE_WHITELIST_URL
+import java.io.File
+import javax.inject.Inject
+import kotlin.Exception
+
+class RulesEraser @Inject constructor(
+ private val preferences: PreferenceRepository,
+ private val pathVars: PathVars,
+ private val remixExistingRulesWorkManager: RemixExistingRulesWorkManager,
+ private val updateRemoteDnsRulesManager: UpdateRemoteRulesWorkManager,
+ private val updateLocalRulesWorkManager: UpdateLocalRulesWorkManager
+) {
+
+ var callback: OnRulesErased? = null
+
+ @WorkerThread
+ fun eraseRules(ruleType: DnsRuleType) {
+ stopRelatedWorks(ruleType)
+ Thread.sleep(500)
+ getFiles(ruleType).forEach {
+ eraseFile(it)
+ }
+ erasePreference(ruleType)
+ callback?.onRulesEraseFinished()
+ }
+
+ private fun getFiles(ruleType: DnsRuleType) =
+ when (ruleType) {
+ DnsRuleType.BLACKLIST -> listOf(
+ pathVars.dnsCryptBlackListPath,
+ pathVars.dnsCryptSingleBlackListPath,
+ pathVars.dnsCryptLocalBlackListPath,
+ pathVars.dnsCryptRemoteBlackListPath
+ )
+
+ DnsRuleType.IP_BLACKLIST -> listOf(
+ pathVars.dnsCryptIPBlackListPath,
+ pathVars.dnsCryptSingleIPBlackListPath,
+ pathVars.dnsCryptLocalIPBlackListPath,
+ pathVars.dnsCryptRemoteIPBlackListPath
+ )
+
+ DnsRuleType.WHITELIST -> listOf(
+ pathVars.dnsCryptWhiteListPath,
+ pathVars.dnsCryptSingleWhiteListPath,
+ pathVars.dnsCryptLocalWhiteListPath,
+ pathVars.dnsCryptRemoteWhiteListPath
+ )
+
+ DnsRuleType.CLOAKING -> listOf(
+ pathVars.dnsCryptCloakingRulesPath,
+ pathVars.dnsCryptSingleCloakingRulesPath,
+ pathVars.dnsCryptLocalCloakingRulesPath,
+ pathVars.dnsCryptRemoteCloakingRulesPath
+ )
+
+ DnsRuleType.FORWARDING -> listOf(
+ pathVars.dnsCryptForwardingRulesPath,
+ pathVars.dnsCryptSingleForwardingRulesPath,
+ pathVars.dnsCryptLocalForwardingRulesPath,
+ pathVars.dnsCryptRemoteForwardingRulesPath
+ )
+ }
+
+ private fun eraseFile(filePath: String) {
+
+ var eraseText = ""
+ if (filePath == pathVars.dnsCryptCloakingRulesPath
+ || filePath == pathVars.dnsCryptSingleCloakingRulesPath
+ ) {
+ eraseText = pathVars.dnsCryptDefaultCloakingRule
+ } else if (filePath == pathVars.dnsCryptForwardingRulesPath
+ || filePath == pathVars.dnsCryptSingleForwardingRulesPath
+ ) {
+ eraseText = pathVars.dnsCryptDefaultForwardingRule
+ }
+
+ try {
+ val file = File(filePath)
+ if (file.isFile) {
+ file.printWriter().use { it.println(eraseText) }
+ }
+ } catch (e: Exception) {
+ loge("EraseRules", e)
+ }
+
+ }
+
+ private fun erasePreference(ruleType: DnsRuleType) {
+ preferences.setStringPreference(getRemoteRulesUrlPreferenceKey(ruleType), "")
+ }
+
+ private fun getRemoteRulesUrlPreferenceKey(ruleType: DnsRuleType) =
+ when (ruleType) {
+ DnsRuleType.BLACKLIST -> REMOTE_BLACKLIST_URL
+ DnsRuleType.IP_BLACKLIST -> REMOTE_IP_BLACKLIST_URL
+ DnsRuleType.WHITELIST -> REMOTE_WHITELIST_URL
+ DnsRuleType.CLOAKING -> REMOTE_CLOAKING_URL
+ DnsRuleType.FORWARDING -> REMOTE_FORWARDING_URL
+ }
+
+ private fun stopRelatedWorks(ruleType: DnsRuleType) {
+ remixExistingRulesWorkManager.stopMix(ruleType)
+ updateRemoteDnsRulesManager.stopRefreshDnsRules(ruleType)
+ updateLocalRulesWorkManager.stopImportDnsRules(ruleType)
+ }
+
+ interface OnRulesErased {
+ fun onRulesEraseFinished()
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt
index 08bf1b3c9..e72cada79 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/FirewallFragment.kt
@@ -47,9 +47,11 @@ import pan.alexander.tordnscrypt.di.SharedPreferencesModule
import pan.alexander.tordnscrypt.dialogs.NotificationHelper
import pan.alexander.tordnscrypt.dialogs.NotificationHelper.TAG_HELPER
import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository
+import pan.alexander.tordnscrypt.modules.ModulesAux
import pan.alexander.tordnscrypt.modules.ModulesStatus
import pan.alexander.tordnscrypt.settings.OnBackPressListener
import pan.alexander.tordnscrypt.settings.firewall.adapter.FirewallAdapter
+import pan.alexander.tordnscrypt.utils.enums.ModuleState
import pan.alexander.tordnscrypt.utils.enums.OperationMode
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.FIREWALL_ENABLED
@@ -105,7 +107,7 @@ class FirewallFragment : Fragment(),
private var searchText: String? = null
- var firewallEnabled = false
+ private var firewallEnabled = false
override fun onCreate(savedInstanceState: Bundle?) {
App.instance.daggerComponent.inject(this)
@@ -244,6 +246,7 @@ class FirewallFragment : Fragment(),
if (firewallFirstStart) {
activateAll(true)
viewModel.activateAllFirsStart()
+ startFirewall()
}
when {
@@ -353,7 +356,7 @@ class FirewallFragment : Fragment(),
R.id.btnTopUnCheckAllFirewall -> activateAll(false)
R.id.btnPowerFirewall -> {
enableFirewall()
- modulesStatus.setIptablesRulesUpdateRequested(context, true)
+ startFirewall()
}
else -> loge("FirewallFragment onClick unknown id: ${v.id}")
}
@@ -404,8 +407,10 @@ class FirewallFragment : Fragment(),
if (buttonView.id == R.id.menu_switch) {
if (isChecked) {
enableFirewall()
+ startFirewall()
} else {
disableFirewall()
+ stopFirewall()
}
modulesStatus.setIptablesRulesUpdateRequested(context, true)
}
@@ -460,6 +465,20 @@ class FirewallFragment : Fragment(),
binding.btnPowerFirewall.setOnClickListener(this)
}
+ private fun startFirewall() {
+ if (modulesStatus.firewallState != ModuleState.RUNNING) {
+ modulesStatus.setFirewallState(ModuleState.STARTING, preferenceRepository.get())
+ ModulesAux.makeModulesStateExtraLoop(context)
+ }
+ }
+
+ private fun stopFirewall() {
+ if (modulesStatus.firewallState != ModuleState.STOPPED) {
+ modulesStatus.setFirewallState(ModuleState.STOPPING, preferenceRepository.get())
+ ModulesAux.makeModulesStateExtraLoop(context)
+ }
+ }
+
private fun searchApps(text: String?) {
searchText = text?.trim()?.lowercase()
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt
index 35175912e..f15f861bb 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/SaveFirewallChangesDialog.kt
@@ -23,7 +23,6 @@ import androidx.appcompat.app.AlertDialog
import pan.alexander.tordnscrypt.R
import pan.alexander.tordnscrypt.dialogs.ExtendedDialogFragment
import pan.alexander.tordnscrypt.modules.ModulesStatus
-import pan.alexander.tordnscrypt.utils.enums.ModuleState
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
import java.lang.Exception
@@ -41,16 +40,7 @@ class SaveFirewallChangesDialog : ExtendedDialogFragment() {
val firewallFragment = parentFragmentManager.findFragmentByTag(FirewallFragment.TAG)
as? FirewallFragment ?: return null
- val modulesRunning = modulesStatus.dnsCryptState == ModuleState.RUNNING
- || modulesStatus.torState == ModuleState.RUNNING
- val firewallEnabled = firewallFragment.firewallEnabled
-
- val message = if (!firewallEnabled || firewallEnabled && modulesRunning) {
- activity.getString(R.string.ask_save_changes)
- } else {
- activity.getString(R.string.ask_save_changes) + "\n\t\n" +
- activity.getString(R.string.firewall_warning_enable_module)
- }
+ val message = activity.getString(R.string.ask_save_changes)
val builder = AlertDialog.Builder(activity)
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt
index e2788621f..3160a0d26 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/firewall/adapter/FirewallAdapter.kt
@@ -180,15 +180,15 @@ class FirewallAdapter(
private val cardAppFirewall = itemView.findViewById(R.id.cardAppFirewall)
private val imgAppIconFirewall = itemView.findViewById(R.id.imgAppIconFirewall)
private val btnLanFirewall = itemView.findViewById(R.id.btnLanFirewall)
- .also { it.setOnClickListener(this) }
+ ?.also { it.setOnClickListener(this) }
private val btnWifiFirewall = itemView.findViewById(R.id.btnWifiFirewall)
- .also { it.setOnClickListener(this) }
+ ?.also { it.setOnClickListener(this) }
private val btnGsmFirewall = itemView.findViewById(R.id.btnGsmFirewall)
- .also { it.setOnClickListener(this) }
+ ?.also { it.setOnClickListener(this) }
private val btnRoamingFirewall = itemView.findViewById(R.id.btnRoamingFirewall)
- .also { it.setOnClickListener(this) }
+ ?.also { it.setOnClickListener(this) }
private val btnVpnFirewall = itemView.findViewById(R.id.btnVpnFirewall)
- .also { it.setOnClickListener(this) }
+ ?.also { it.setOnClickListener(this) }
private val tvAppName = itemView.findViewById(R.id.tvAppName)
fun bind(position: Int) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java
new file mode 100644
index 000000000..95bdc475c
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ITPDSubscriptionsFragment.java
@@ -0,0 +1,276 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.itpd_settings;
+
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import pan.alexander.tordnscrypt.App;
+import pan.alexander.tordnscrypt.R;
+import pan.alexander.tordnscrypt.databinding.FragmentItpdSubscriptionBinding;
+import pan.alexander.tordnscrypt.databinding.ItemRulesBinding;
+
+import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
+
+import javax.inject.Inject;
+
+
+public class ITPDSubscriptionsFragment extends Fragment implements View.OnClickListener {
+
+ @Inject
+ public ViewModelProvider.Factory viewModelFactory;
+
+ private ItpdSubscriptionsViewModel viewModel;
+
+ private FragmentItpdSubscriptionBinding binding;
+
+ private SubscriptionsAdapter adapter;
+
+
+ public ITPDSubscriptionsFragment() {
+ }
+
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ App.getInstance().getDaggerComponent().inject(this);
+ super.onCreate(savedInstanceState);
+
+ viewModel = new ViewModelProvider(this, viewModelFactory).get(ItpdSubscriptionsViewModel.class);
+
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ setTitle(activity);
+ }
+
+ private void setTitle(Activity activity) {
+ activity.setTitle(R.string.pref_itpd_addressbook_subscriptions);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ try {
+ binding = FragmentItpdSubscriptionBinding.inflate(inflater, container, false);
+ } catch (Exception e) {
+ loge("ShowRulesRecycleFrag onCreateView", e);
+ throw e;
+ }
+
+ initRecycler();
+ initFab();
+
+ return binding.getRoot();
+ }
+
+ private void initRecycler() {
+ RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(requireContext());
+ binding.rvRules.setLayoutManager(mLayoutManager);
+ adapter = new SubscriptionsAdapter();
+ binding.rvRules.setAdapter(adapter);
+ }
+
+ private void initFab() {
+ FloatingActionButton btnAddRule = binding.floatingBtnAddRule;
+ btnAddRule.setAlpha(0.8f);
+ btnAddRule.setOnClickListener(this);
+ btnAddRule.requestFocus();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ observeSubscriptions();
+ requestSubscriptions();
+ }
+
+ private void observeSubscriptions() {
+ viewModel.getSubscriptions().observe(getViewLifecycleOwner(), subscriptions -> {
+ if (subscriptions != null) {
+ adapter.updateSubscriptions(subscriptions);
+ }
+ });
+ }
+
+ private void requestSubscriptions() {
+ viewModel.requestSubscriptions();
+ }
+
+ @Override
+ public void onClick(View v) {
+ adapter.addSubscription();
+ }
+
+ private class SubscriptionsAdapter extends RecyclerView.Adapter {
+
+ private final List subscriptions = new ArrayList<>();
+
+ @SuppressLint("NotifyDataSetChanged")
+ void updateSubscriptions(List subscriptions) {
+ this.subscriptions.clear();
+ for (ItpdSubscriptionRecycleItem item : subscriptions) {
+ this.subscriptions.add(new ItpdSubscriptionRecycleItem(item.getText()));
+ }
+ notifyDataSetChanged();
+ }
+
+ void addSubscription() {
+ int position = subscriptions.size();
+ subscriptions.add(
+ position,
+ new ItpdSubscriptionRecycleItem("")
+ );
+ notifyItemInserted(position);
+ binding.rvRules.scrollToPosition(position);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view;
+ try {
+ view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_rules, parent, false);
+ } catch (Exception e) {
+ loge("ShowRulesRecycleFrag onCreateViewHolder", e);
+ throw e;
+ }
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.bind(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return subscriptions.size();
+ }
+
+ ItpdSubscriptionRecycleItem getRule(int position) {
+ return subscriptions.get(position);
+ }
+
+ void delRule(int position) {
+ subscriptions.remove(position);
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder
+ implements View.OnClickListener, View.OnFocusChangeListener {
+
+ ItemRulesBinding itemRulesBinding;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+
+ itemRulesBinding = ItemRulesBinding.bind(itemView);
+
+ itemRulesBinding.etRule.addTextChangedListener(textWatcher);
+ itemRulesBinding.delBtnRules.setOnClickListener(this);
+ }
+
+ void bind(int position) {
+ itemRulesBinding.etRule.setText(getRule(position).getText(), TextView.BufferType.EDITABLE);
+ itemRulesBinding.swRuleActive.setVisibility(View.GONE);
+ }
+
+ TextWatcher textWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ int position = getBindingAdapterPosition();
+ if (position != NO_POSITION) {
+ getRule(position).setText(s.toString());
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ };
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.delBtnRules) {
+ int position = getBindingAdapterPosition();
+ if (position != NO_POSITION) {
+ delRule(position);
+ notifyItemRemoved(position);
+ }
+ }
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ int position = getBindingAdapterPosition();
+ if (position != NO_POSITION) {
+ binding.rvRules.smoothScrollToPosition(position);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ viewModel.saveSubscriptions(requireContext(), adapter.subscriptions);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ binding = null;
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java
new file mode 100644
index 000000000..53d6f4b7f
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionRecycleItem.java
@@ -0,0 +1,62 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.itpd_settings;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+public class ItpdSubscriptionRecycleItem {
+
+ private String text;
+
+ public ItpdSubscriptionRecycleItem(String text) {
+ this.text = text;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ItpdSubscriptionRecycleItem that = (ItpdSubscriptionRecycleItem) o;
+ return text.equals(that.text);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(text);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "ItpdSubscriptionRecycleItem{" +
+ "text='" + text + '\'' +
+ '}';
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt
new file mode 100644
index 000000000..67e2eeebe
--- /dev/null
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/ItpdSubscriptionsViewModel.kt
@@ -0,0 +1,145 @@
+/*
+ This file is part of InviZible Pro.
+
+ InviZible Pro 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.
+
+ InviZible Pro 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 InviZible Pro. If not, see .
+
+ Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
+ */
+
+package pan.alexander.tordnscrypt.settings.itpd_settings
+
+import android.content.Context
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
+import pan.alexander.tordnscrypt.R
+import pan.alexander.tordnscrypt.di.CoroutinesModule
+import pan.alexander.tordnscrypt.modules.ModulesAux
+import pan.alexander.tordnscrypt.modules.ModulesRestarter
+import pan.alexander.tordnscrypt.settings.PathVars
+import pan.alexander.tordnscrypt.utils.Constants.URL_REGEX
+import pan.alexander.tordnscrypt.utils.logger.Logger.loge
+import java.io.File
+import javax.inject.Inject
+import javax.inject.Named
+
+class ItpdSubscriptionsViewModel @Inject constructor(
+ private val pathVars: PathVars,
+ @Named(CoroutinesModule.DISPATCHER_IO)
+ private val dispatcherIo: CoroutineDispatcher,
+ @Named(CoroutinesModule.SUPERVISOR_JOB_IO_DISPATCHER_SCOPE)
+ private val baseCoroutineScope: CoroutineScope,
+ coroutineExceptionHandler: CoroutineExceptionHandler
+) : ViewModel() {
+
+ private val scope: CoroutineScope =
+ baseCoroutineScope + CoroutineName("ItpdSubscriptionsViewModelCoroutine") + coroutineExceptionHandler
+
+ private val subscriptionsMutable = MutableLiveData?>(null)
+ val subscriptions: LiveData?> get() = subscriptionsMutable
+
+ fun requestSubscriptions() {
+ viewModelScope.launch(dispatcherIo) {
+ val itpdConf = getItpdConf()
+ var header = ""
+ for (line in itpdConf) {
+ if (line.startsWith("[") && line.endsWith("]")) {
+ header = line.trim { it == '[' || it == ']' }
+ } else if (header == "addressbook" && line.startsWith("subscriptions = ")) {
+ subscriptionsMutable.postValue(
+ line.removePrefix("subscriptions = ")
+ .split(",")
+ .map { ItpdSubscriptionRecycleItem(it.trim()) }
+ )
+ break
+ }
+ }
+ }
+ }
+
+ fun saveSubscriptions(context: Context, subscriptions: List) {
+ scope.launch {
+
+ val savedSubscriptions = this@ItpdSubscriptionsViewModel.subscriptions.value
+
+ if (subscriptions.size == savedSubscriptions?.size
+ && subscriptions.containsAll(savedSubscriptions)
+ ) {
+ return@launch
+ }
+
+ val subscriptionLine = subscriptions.toSet().filter {
+ it.text.matches(Regex(URL_REGEX))
+ }.takeIf {
+ it.isNotEmpty()
+ }?.joinToString(", ") {
+ it.text
+ } ?: context.resources.getStringArray(R.array.default_itpd_subscriptions)
+ .joinToString(", ")
+
+ val itpdConf = getItpdConf().toMutableList()
+ if (itpdConf.isEmpty()) {
+ return@launch
+ }
+
+ var header = ""
+ for (i in itpdConf.indices) {
+ val line = itpdConf[i]
+ if (line.startsWith("[") && line.endsWith("]")) {
+ header = line.trim { it == '[' || it == ']' }
+ } else if (header == "addressbook" && line.startsWith("subscriptions = ")) {
+ itpdConf[i] = "subscriptions = $subscriptionLine"
+ if (line != itpdConf[i]) {
+ break
+ } else {
+ return@launch
+ }
+ }
+ }
+
+ saveItpdConf(itpdConf)
+
+ restartItpdIfNeeded(context)
+ }
+ }
+
+ private fun getItpdConf(): List = try {
+ File(pathVars.itpdConfPath).readLines()
+ } catch (e: Exception) {
+ loge("ItpdSubscriptionsViewModel getItpdConf", e)
+ emptyList()
+ }
+
+ private fun saveItpdConf(lines: List) = try {
+ File(pathVars.itpdConfPath).printWriter().use {
+ lines.forEach { line -> it.println(line) }
+ }
+ } catch (e: Exception) {
+ loge("ItpdSubscriptionsViewModel saveItpdConf", e)
+ }
+
+ private fun restartItpdIfNeeded(context: Context) {
+ val itpdRunning = ModulesAux.isITPDSavedStateRunning()
+ if (itpdRunning) {
+ ModulesRestarter.restartITPD(context)
+ }
+ }
+}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java
similarity index 98%
rename from tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java
rename to tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java
index aec149281..85ebe6fa0 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/PreferencesITPDFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/itpd_settings/PreferencesITPDFragment.java
@@ -17,7 +17,7 @@
Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
*/
-package pan.alexander.tordnscrypt.settings;
+package pan.alexander.tordnscrypt.settings.itpd_settings;
import android.app.Activity;
import android.content.Context;
@@ -42,6 +42,9 @@
import pan.alexander.tordnscrypt.R;
import pan.alexander.tordnscrypt.modules.ModulesAux;
import pan.alexander.tordnscrypt.modules.ModulesStatus;
+import pan.alexander.tordnscrypt.settings.ConfigEditorFragment;
+import pan.alexander.tordnscrypt.settings.PathVars;
+import pan.alexander.tordnscrypt.settings.SettingsActivity;
import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor;
import pan.alexander.tordnscrypt.utils.filemanager.FileManager;
import pan.alexander.tordnscrypt.modules.ModulesRestarter;
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java
deleted file mode 100644
index 03900a83a..000000000
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/show_rules/ShowRulesRecycleFrag.java
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- This file is part of InviZible Pro.
-
- InviZible Pro 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.
-
- InviZible Pro 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 InviZible Pro. If not, see .
-
- Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
- */
-
-package pan.alexander.tordnscrypt.settings.show_rules;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-
-import androidx.appcompat.widget.SwitchCompat;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.preference.PreferenceManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import pan.alexander.tordnscrypt.R;
-import pan.alexander.tordnscrypt.settings.SettingsActivity;
-import pan.alexander.tordnscrypt.dialogs.NotificationDialogFragment;
-import pan.alexander.tordnscrypt.modules.ModulesAux;
-import pan.alexander.tordnscrypt.utils.filemanager.FileManager;
-import pan.alexander.tordnscrypt.modules.ModulesRestarter;
-
-import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
-
-
-public class ShowRulesRecycleFrag extends Fragment implements View.OnClickListener {
-
- private RecyclerView mRecyclerView;
- private RecyclerView.Adapter mAdapter;
-
- private final ArrayList rules_file = new ArrayList<>();
- private final ArrayList rules_list = new ArrayList<>();
- private final ArrayList others_list = new ArrayList<>();
- private final ArrayList original_rules = new ArrayList<>();
-
- private String file_path;
- private boolean readOnly;
-
-
- public ShowRulesRecycleFrag() {
- }
-
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setRetainInstance(true);
-
- if (getArguments() != null) {
- List rules = getArguments().getStringArrayList("rules_file");
- rules_file.addAll(rules != null ? rules : Collections.emptyList());
- file_path = getArguments().getString("path");
- }
-
- }
-
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
-
- View view;
- try {
- view = inflater.inflate(R.layout.fragment_show_rules_recycle, container, false);
- } catch (Exception e) {
- loge("ShowRulesRecycleFrag onCreateView", e);
- throw e;
- }
-
- RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(container.getContext());
- mRecyclerView = view.findViewById(R.id.rvRules);
- mRecyclerView.setLayoutManager(mLayoutManager);
-
- if (checkAndTrimIfRulesTooMany()) {
- readOnly = true;
- showTooManyRulesDialog();
- }
-
- FloatingActionButton btnAddRule = view.findViewById(R.id.floatingBtnAddRule);
- if (readOnly) {
- btnAddRule.setVisibility(View.GONE);
- } else {
- btnAddRule.setAlpha(0.8f);
- btnAddRule.setOnClickListener(this);
- btnAddRule.requestFocus();
- }
-
- return view;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- setTitle(activity);
-
- if (rules_list.isEmpty()) {
- fillRules();
- }
-
- mAdapter = new RulesAdapter(rules_list);
- mRecyclerView.setAdapter(mAdapter);
- }
-
- @Override
- public void onStop() {
- super.onStop();
-
- Context context = getActivity();
- if (context == null) {
- return;
- }
-
- List rules_file_new = new LinkedList<>();
-
- for (Rules rule : rules_list) {
- if (rule.active) {
- rules_file_new.add(rule.text);
- } else {
- rules_file_new.add("#" + rule.text);
- }
- rules_file_new.add("");
- }
-
- if (rules_file_new.equals(original_rules)) return;
-
- if (file_path.contains("subscriptions")) {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
- StringBuilder sb = new StringBuilder();
- String str = "";
- for (Rules rule : rules_list) {
- if (rule.subscription)
- sb.append(rule.text).append(", ");
- }
- if (sb.length() > 2) {
- str = sb.substring(0, sb.length() - 2);
- }
- sp.edit().putString("subscriptions", str).apply();
-
- rules_file.clear();
- rules_file.addAll(rules_file_new);
- original_rules.clear();
- original_rules.addAll(rules_file_new);
- } else {
- rules_file.clear();
- rules_file.addAll(others_list);
- rules_file.addAll(rules_file_new);
- original_rules.clear();
- original_rules.addAll(rules_file_new);
-
- FileManager.writeToTextFile(context, file_path, rules_file, SettingsActivity.rules_tag);
- }
-
- boolean dnsCryptRunning = ModulesAux.isDnsCryptSavedStateRunning();
- boolean itpdRunning = ModulesAux.isITPDSavedStateRunning();
-
- if (itpdRunning && file_path.contains("subscriptions")) {
- ModulesRestarter.restartITPD(context);
- } else if (dnsCryptRunning) {
- ModulesRestarter.restartDNSCrypt(context);
- }
- }
-
- private boolean checkAndTrimIfRulesTooMany() {
- if (rules_file.size() > 1000) {
- rules_file.subList(1000, rules_file.size()).clear();
- rules_file.trimToSize();
-
- return true;
- }
-
- return false;
- }
-
- private void showTooManyRulesDialog() {
- DialogFragment dialogFragment = NotificationDialogFragment.newInstance(R.string.dnscrypt_many_rules_dialog_message);
- if (isAdded()) {
- dialogFragment.show(getChildFragmentManager(), "TooManyRules");
- }
- }
-
- private void setTitle(Activity activity) {
- if (file_path.endsWith("forwarding-rules.txt")) {
- activity.setTitle(R.string.title_dnscrypt_forwarding_rules);
- } else if (file_path.endsWith("cloaking-rules.txt")) {
- activity.setTitle(R.string.title_dnscrypt_cloaking_rules);
- } else if (file_path.endsWith("ip-blacklist.txt")) {
- activity.setTitle(R.string.title_dnscrypt_ip_blacklist);
- } else if (file_path.endsWith("blacklist.txt")) {
- activity.setTitle(R.string.title_dnscrypt_blacklist);
- } else if (file_path.endsWith("whitelist.txt")) {
- activity.setTitle(R.string.title_dnscrypt_whitelist);
- } else if (file_path.endsWith("subscriptions")) {
- activity.setTitle(R.string.pref_itpd_addressbook_subscriptions);
- }
- }
-
- private void fillRules() {
- String[] lockedItems = {".i2p", "onion"};
-
- for (int i = 0; i < rules_file.size(); i++) {
- boolean match = !rules_file.get(i).matches("#.*#.*") && !rules_file.get(i).isEmpty();
- boolean active = !rules_file.get(i).contains("#");
- boolean locked = false;
- boolean subscription = file_path.contains("subscriptions") && !rules_file.get(i).isEmpty();
-
- for (String str : lockedItems) {
- if (rules_file.get(i).matches(".?" + str + ".*")) {
- locked = true;
- break;
- }
- }
- if (match) {
- rules_list.add(new Rules(rules_file.get(i).replace("#", ""), active, locked, subscription));
- original_rules.add(rules_file.get(i));
- original_rules.add("");
- } else if (!rules_file.get(i).isEmpty()) {
- others_list.add(rules_file.get(i));
- others_list.add("");
- }
- }
- }
-
- @Override
- public void onClick(View v) {
- boolean subscription = file_path.contains("subscriptions");
- rules_list.add(new Rules("", true, false, subscription));
- mAdapter.notifyItemInserted(rules_list.size() - 1);
- mRecyclerView.scrollToPosition(rules_list.size() - 1);
- }
-
- class RulesAdapter extends RecyclerView.Adapter {
-
- ArrayList list_rules_adapter;
- LayoutInflater lInflater = (LayoutInflater) requireActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- RulesAdapter(ArrayList rules_list) {
- list_rules_adapter = rules_list;
- }
-
-
- @NonNull
- @Override
- public RuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View view;
- try {
- view = lInflater.inflate(R.layout.item_rules, parent, false);
- } catch (Exception e) {
- loge("ShowRulesRecycleFrag onCreateViewHolder", e);
- throw e;
- }
- return new RuleViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(@NonNull RulesAdapter.RuleViewHolder holder, int position) {
- holder.bind(position);
- }
-
- @Override
- public int getItemCount() {
- return list_rules_adapter.size();
- }
-
- Rules getRule(int position) {
- return list_rules_adapter.get(position);
- }
-
- void delRule(int position) {
-
- try {
- list_rules_adapter.remove(position);
- } catch (Exception e) {
- loge("ShowRulesRecycleFrag getItemCount", e);
- }
-
-
- }
-
- class RuleViewHolder extends RecyclerView.ViewHolder
- implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, View.OnFocusChangeListener {
-
- EditText etRule;
- ImageButton delBtnRules;
- SwitchCompat swRuleActive;
-
- RuleViewHolder(View itemView) {
- super(itemView);
-
- etRule = itemView.findViewById(R.id.etRule);
- delBtnRules = itemView.findViewById(R.id.delBtnRules);
- swRuleActive = itemView.findViewById(R.id.swRuleActive);
-
- if (readOnly) {
- delBtnRules.setVisibility(View.GONE);
- swRuleActive.setVisibility(View.GONE);
- } else {
- if (!file_path.contains("subscriptions")) {
- swRuleActive.setOnCheckedChangeListener(this);
- swRuleActive.setOnFocusChangeListener(this);
- }
- etRule.addTextChangedListener(textWatcher);
- delBtnRules.setOnClickListener(this);
- }
- }
-
- void bind(int position) {
- etRule.setText(getRule(position).text, TextView.BufferType.EDITABLE);
- etRule.setEnabled(getRule(position).active);
-
- if (getRule(position).subscription) {
- swRuleActive.setVisibility(View.GONE);
- } else if (!readOnly) {
- swRuleActive.setVisibility(View.VISIBLE);
- swRuleActive.setChecked(getRule(position).active);
- }
-
- delBtnRules.setEnabled(true);
-
- if (getRule(position).locked) {
- delBtnRules.setEnabled(false);
- }
- }
-
- TextWatcher textWatcher = new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- int position = getBindingAdapterPosition();
- if (!getRule(position).locked)
- getRule(position).text = s.toString();
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- }
- };
-
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.delBtnRules) {
- int position = getBindingAdapterPosition();
- delRule(position);
- notifyItemRemoved(position);
- }
- }
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- int position = getBindingAdapterPosition();
- if (getRule(position).active != isChecked) {
- getRule(position).active = isChecked;
- notifyItemChanged(position);
- }
- }
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- mRecyclerView.smoothScrollToPosition(getBindingAdapterPosition());
- }
- }
- }
- }
-
-}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java
index 95bb41b65..0226a7790 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridges.java
@@ -93,6 +93,7 @@
import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
import static pan.alexander.tordnscrypt.utils.Constants.IPv6_REGEX_NO_BOUNDS;
+import static pan.alexander.tordnscrypt.utils.Utils.unescapeHTML;
import static pan.alexander.tordnscrypt.utils.enums.BridgeType.conjure;
import static pan.alexander.tordnscrypt.utils.enums.BridgeType.webtunnel;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
@@ -384,7 +385,7 @@ public void onStop() {
if (fascistFirewallShouldBeDisabled && line.startsWith("ReachableAddresses")) {
line = "#" + line;
}
- if ((line.contains("#")
+ if ((line.startsWith("#")
|| (!line.contains("Bridge ")
&& !line.contains("ClientTransportPlugin ")
&& !line.contains("UseBridges ")))
@@ -744,7 +745,7 @@ private void addBridges(final List persistList) {
builder.setPositiveButton(getText(R.string.ok), (dialogInterface, i) -> {
List bridgesListNew = new ArrayList<>();
- String inputLinesStr = input.getText().toString().trim();
+ String inputLinesStr = unescapeHTML(input.getText().toString().trim());
String bridgeBase;
if (isBridgeIPv6(inputLinesStr)) {
@@ -843,7 +844,7 @@ private boolean isBridgeIPv6(String bridge) {
private void addRequestedBridges(String bridgesToAdd, List savedCustomBridges) {
List bridgesListNew = new ArrayList<>();
- String[] bridgesArrNew = bridgesToAdd.split("\n");
+ String[] bridgesArrNew = unescapeHTML(bridgesToAdd).split("\n");
if (bridgesArrNew.length != 0) {
for (String brgNew : bridgesArrNew) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt
index cd3c85ff9..05dacd0eb 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/PreferencesTorBridgesViewModel.kt
@@ -252,6 +252,8 @@ class PreferencesTorBridgesViewModel @Inject constructor(
logw("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e)
} catch (e: java.util.concurrent.CancellationException) {
logw("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e)
+ } catch (e: IllegalStateException) {
+ requestTorBridges(transport, ipv6Bridges, "", "")
} catch (e: Exception) {
e.message?.let { showErrorMessage(it) }
loge("PreferencesTorBridgesViewModel requestTorBridgesCaptchaChallenge", e)
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java
index 9325a994f..672131cb1 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/settings/tor_bridges/SnowflakeConfigurator.java
@@ -44,10 +44,9 @@ public class SnowflakeConfigurator {
private static final int SOCKS_ARGUMENT_MAX_LENGTH = 510;
private static final int AMP_CACHE = 1;
- private static final int FASTLY = 2;
- private static final int CDN77 = 3;
- private static final int AZURE = 4;
- private static final int AMAZON = 5;
+ private static final int CDN77 = 2;
+ private static final int AZURE = 3;
+ private static final int AMAZON = 4;
private final Context context;
private final Lazy defaultPreferences;
@@ -118,8 +117,6 @@ private String getURL(int rendezvousType) {
if (rendezvous == AMP_CACHE) {
return "https://snowflake-broker.torproject.net/"
+ " ampcache=https://cdn.ampproject.org/";
- } else if (rendezvous == FASTLY) {
- return "https://snowflake-broker.torproject.net.global.prod.fastly.net/";
} else if (rendezvous == CDN77) {
return "https://1098762253.rsc.cdn77.org/";
} else if (rendezvous == AZURE) {
@@ -133,8 +130,6 @@ private String getFront(int rendezvousType) {
int rendezvous = getRendezvous(rendezvousType);
if (rendezvous == AMP_CACHE) {
return "www.google.com,cdn.ampproject.org";
- } else if (rendezvous == FASTLY) {
- return "github.githubassets.com,www.shazam.com,www.cosmopolitan.com,www.esquire.com";
} else if (rendezvous == CDN77) {
return "docs.plesk.com,www.phpmyadmin.net,app.datapacket.com";
} else if (rendezvous == AZURE) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java
index 59e98687b..672d8d789 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/tor_fragment/TorRunFragment.java
@@ -43,13 +43,19 @@
import android.widget.ScrollView;
import android.widget.TextView;
+import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.MainActivity;
import pan.alexander.tordnscrypt.R;
import pan.alexander.tordnscrypt.TopFragment;
+import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.utils.root.RootExecService;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static pan.alexander.tordnscrypt.TopFragment.TorVersion;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.FAULT;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import com.google.android.material.divider.MaterialDivider;
@@ -199,10 +205,28 @@ public void onDestroyView() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnTorStart) {
+ letFirewallStop();
presenter.startButtonOnClick();
}
}
+ private void letFirewallStop() {
+ ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if (modulesStatus.getFirewallState() != STOPPED
+ && modulesStatus.getTorState() == RUNNING
+ && (modulesStatus.getDnsCryptState() == STOPPED
+ || modulesStatus.getDnsCryptState() == STOPPING
+ || modulesStatus.getDnsCryptState() == FAULT)
+ && (modulesStatus.getItpdState() == STOPPED
+ || modulesStatus.getItpdState() == STOPPING
+ || modulesStatus.getItpdState() == FAULT)) {
+ modulesStatus.setFirewallState(
+ STOPPING,
+ App.getInstance().getDaggerComponent().getPreferenceRepository().get()
+ );
+ }
+ }
+
@Override
public void setTorStatus(int resourceText, int resourceColor) {
tvTorStatus.setText(resourceText);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java
index 88eb86670..304a9ce3e 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Constants.java
@@ -90,7 +90,6 @@ public interface Constants {
String DNSCRYPT_RESOLVERS_SOURCE_IPV6 = "https://ipv6.download.dnscrypt.info/resolvers-list/v3/public-resolvers.md";
String DNSCRYPT_RELAYS_SOURCE_IPV6 = "https://ipv6.download.dnscrypt.info/resolvers-list/v3/relays.md";
- int SITES_IPS_REFRESH_JOB_ID = 1;
int DEFAULT_SITES_IPS_REFRESH_INTERVAL = 12;
int NFLOG_GROUP = 78;
@@ -110,4 +109,5 @@ public interface Constants {
String NUMBER_REGEX = "\\d+";
String HOST_NAME_REGEX = "[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)";
+ String URL_REGEX = "(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)";
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt
index 0e3ace2b1..c26104b54 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/Utils.kt
@@ -32,6 +32,7 @@ import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Process
+import android.text.Html
import android.util.Base64
import android.util.TypedValue
import android.view.Display
@@ -55,7 +56,10 @@ import pan.alexander.tordnscrypt.utils.filemanager.FileShortener
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
import pan.alexander.tordnscrypt.utils.logger.Logger.logi
import pan.alexander.tordnscrypt.utils.logger.Logger.logw
-import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.*
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.ALWAYS_SHOW_HELP_MESSAGES
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.CHILD_LOCK_PASSWORD
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.DEFAULT_BRIDGES_OBFS
+import pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.OWN_BRIDGES_OBFS
import pan.alexander.tordnscrypt.utils.root.RootCommands
import pan.alexander.tordnscrypt.utils.root.RootCommandsMark.NULL_MARK
import java.io.File
@@ -63,6 +67,8 @@ import java.io.PrintWriter
import java.net.Inet4Address
import java.net.NetworkInterface
import java.net.SocketException
+import java.util.Locale
+import java.util.regex.Pattern
import kotlin.math.roundToInt
@@ -389,4 +395,37 @@ object Utils {
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
+ @JvmStatic
+ fun unescapeHTML(line: String): String {
+ var result = line
+ val pattern = Pattern.compile("\\d+;")
+ val matcher = pattern.matcher(line)
+ if (matcher.find()) {
+ result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ matcher.replaceAll(
+ Html.fromHtml(matcher.group(), Html.FROM_HTML_MODE_LEGACY).toString()
+ )
+ } else {
+ matcher.replaceAll(Html.fromHtml(matcher.group()).toString())
+ }
+ }
+ return result
+ }
+
+ fun formatFileSizeToReadableUnits(length: Long): String {
+ var bytes = length
+ if (bytes <= 1024) return String.format(Locale.ROOT, "%d B", bytes)
+ var u = 0
+ while (bytes > 1024 * 1024) {
+ u++
+ bytes = bytes shr 10
+ }
+ return String.format(Locale.ROOT, "%.1f %cB", bytes / 1024f, "kMGTPE"[u])
+ }
+
+ fun getDomainNameFromUrl(url: String): String =
+ url.removePrefix("http://")
+ .removePrefix("https://")
+ .substringBefore("/")
+
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java
index b15a4ad9e..25750a040 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/bootcomplete/BootCompleteManager.java
@@ -23,6 +23,7 @@
import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
import static pan.alexander.tordnscrypt.modules.ModulesServiceActions.ACTION_STOP_SERVICE_FOREGROUND;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.ROOT_MODE;
import static pan.alexander.tordnscrypt.utils.enums.OperationMode.UNDEFINED;
@@ -53,11 +54,12 @@
import javax.inject.Named;
import dagger.Lazy;
+import pan.alexander.tordnscrypt.App;
import pan.alexander.tordnscrypt.domain.preferences.PreferenceRepository;
+import pan.alexander.tordnscrypt.modules.ModulesActionSender;
import pan.alexander.tordnscrypt.modules.ModulesAux;
import pan.alexander.tordnscrypt.modules.ModulesKiller;
import pan.alexander.tordnscrypt.modules.ModulesRunner;
-import pan.alexander.tordnscrypt.modules.ModulesService;
import pan.alexander.tordnscrypt.modules.ModulesStatus;
import pan.alexander.tordnscrypt.modules.ModulesStatusBroadcaster;
import pan.alexander.tordnscrypt.settings.PathVars;
@@ -108,7 +110,7 @@ public BootCompleteManager(
this.updateIPsManager = updateIPsManager;
}
- private ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ private final ModulesStatus modulesStatus = ModulesStatus.getInstance();
public void performAction(final Context context, Intent intent) {
@@ -156,11 +158,16 @@ public void performAction(final Context context, Intent intent) {
boolean savedDNSCryptStateRunning = ModulesAux.isDnsCryptSavedStateRunning();
boolean savedTorStateRunning = ModulesAux.isTorSavedStateRunning();
boolean savedITPDStateRunning = ModulesAux.isITPDSavedStateRunning();
+ boolean savedFirewallStateRunning = ModulesAux.isFirewallSavedStateRunning();
+
+ boolean autoStartFirewall = autoStartDNSCrypt || autoStartTor
+ || autoStartITPD && savedFirewallStateRunning;
if (action.equalsIgnoreCase(MY_PACKAGE_REPLACED) || action.equalsIgnoreCase(ALWAYS_ON_VPN)) {
autoStartDNSCrypt = savedDNSCryptStateRunning;
autoStartTor = savedTorStateRunning;
autoStartITPD = savedITPDStateRunning;
+ autoStartFirewall = autoStartDNSCrypt || autoStartTor || savedFirewallStateRunning;
} else if (action.equals(SHELL_SCRIPT_CONTROL)) {
int startDnsCrypt = intent.getIntExtra(MANAGE_DNSCRYPT_EXTRA, -1);
int startTor = intent.getIntExtra(MANAGE_TOR_EXTRA, -1);
@@ -185,13 +192,17 @@ public void performAction(final Context context, Intent intent) {
broadcastItpdState(autoStartITPD);
}
+ autoStartFirewall = autoStartDNSCrypt || autoStartTor
+ || autoStartITPD && savedFirewallStateRunning;
+
logi("SHELL_SCRIPT_CONTROL start: " +
"DNSCrypt " + autoStartDNSCrypt + " Tor " + autoStartTor + " ITPD " + autoStartITPD);
} else {
resetModulesSavedState(preferences);
}
- if (savedDNSCryptStateRunning || savedTorStateRunning || savedITPDStateRunning) {
+ if (savedDNSCryptStateRunning || savedTorStateRunning || savedITPDStateRunning
+ || savedFirewallStateRunning) {
stopServicesForeground(context, mode, fixTTL);
}
@@ -219,28 +230,29 @@ public void performAction(final Context context, Intent intent) {
}
if (autoStartDNSCrypt && autoStartTor && autoStartITPD) {
- startStopRestartModules(true, true, true);
+ startStopRestartModules(true, true, true, autoStartFirewall);
} else if (autoStartDNSCrypt && autoStartTor) {
- startStopRestartModules(true, true, false);
+ startStopRestartModules(true, true, false, autoStartFirewall);
} else if (autoStartDNSCrypt && !autoStartITPD) {
- startStopRestartModules(true, false, false);
+ startStopRestartModules(true, false, false, autoStartFirewall);
updateIPsManager.get().stopRefreshTorUnlockIPs();
} else if (!autoStartDNSCrypt && autoStartTor && !autoStartITPD) {
- startStopRestartModules(false, true, false);
+ startStopRestartModules(false, true, false, autoStartFirewall);
} else if (!autoStartDNSCrypt && !autoStartTor && autoStartITPD) {
- startStopRestartModules(false, false, true);
+ startStopRestartModules(false, false, true, autoStartFirewall);
updateIPsManager.get().stopRefreshTorUnlockIPs();
} else if (!autoStartDNSCrypt && autoStartTor) {
- startStopRestartModules(false, true, true);
+ startStopRestartModules(false, true, true, autoStartFirewall);
} else if (autoStartDNSCrypt) {
- startStopRestartModules(true, false, true);
+ startStopRestartModules(true, false, true, autoStartFirewall);
updateIPsManager.get().stopRefreshTorUnlockIPs();
} else {
- startStopRestartModules(false, false, false);
+ startStopRestartModules(false, false, false, autoStartFirewall);
updateIPsManager.get().stopRefreshTorUnlockIPs();
}
- if ((autoStartDNSCrypt || autoStartTor || autoStartITPD) && (mode == VPN_MODE || fixTTL)) {
+ if ((autoStartDNSCrypt || autoStartTor || autoStartITPD || autoStartFirewall)
+ && (mode == VPN_MODE || fixTTL)) {
final Intent prepareIntent = VpnService.prepare(context);
if (prepareIntent == null) {
@@ -323,7 +335,12 @@ private void startHOTSPOT(PreferenceRepository preferences) {
}
}
- private void startStopRestartModules(boolean autoStartDNSCrypt, boolean autoStartTor, boolean autoStartITPD) {
+ private void startStopRestartModules(
+ boolean autoStartDNSCrypt,
+ boolean autoStartTor,
+ boolean autoStartITPD,
+ boolean autoStartFirewall
+ ) {
ModulesStatus modulesStatus = ModulesStatus.getInstance();
@@ -354,6 +371,15 @@ private void startStopRestartModules(boolean autoStartDNSCrypt, boolean autoStar
modulesStatus.setItpdState(STOPPED);
}
+ if (autoStartFirewall) {
+ modulesStatus.setFirewallState(STARTING, preferenceRepository.get());
+ if (!autoStartDNSCrypt && !autoStartTor) {
+ ModulesAux.makeModulesStateExtraLoop(context);
+ }
+ } else {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
+
saveModulesStateRunning(autoStartDNSCrypt, autoStartTor, autoStartITPD);
}
@@ -368,6 +394,7 @@ private void resetModulesSavedState(PreferenceRepository preferences) {
preferences.setStringPreference(SAVED_DNSCRYPT_STATE_PREF, ModuleState.UNDEFINED.toString());
preferences.setStringPreference(SAVED_TOR_STATE_PREF, ModuleState.UNDEFINED.toString());
preferences.setStringPreference(SAVED_ITPD_STATE_PREF, ModuleState.UNDEFINED.toString());
+ ModulesAux.saveFirewallStateRunning(false);
}
private void runDNSCrypt() {
@@ -401,13 +428,19 @@ private void stopServicesForeground(Context context, OperationMode mode, boolean
Intent stopVPNServiceForeground = new Intent(context, VpnService.class);
stopVPNServiceForeground.setAction(ACTION_STOP_SERVICE_FOREGROUND);
stopVPNServiceForeground.putExtra("showNotification", true);
- context.startForegroundService(stopVPNServiceForeground);
+ if (App.getInstance().isAppForeground()) {
+ try {
+ context.startService(stopVPNServiceForeground);
+ } catch (Exception e) {
+ loge("BootCompleteReceiver stopServicesForeground", e);
+ context.startForegroundService(stopVPNServiceForeground);
+ }
+ } else {
+ context.startForegroundService(stopVPNServiceForeground);
+ }
}
- Intent stopModulesServiceForeground = new Intent(context, ModulesService.class);
- stopModulesServiceForeground.setAction(ACTION_STOP_SERVICE_FOREGROUND);
- context.startForegroundService(stopModulesServiceForeground);
- stopModulesServiceForeground.putExtra("showNotification", true);
+ ModulesActionSender.INSTANCE.sendIntent(context, ACTION_STOP_SERVICE_FOREGROUND);
logi("BootCompleteReceiver stop running services foreground");
}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt
index 831e9dc15..07729ff92 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/connectionchecker/NetworkChecker.kt
@@ -29,10 +29,8 @@ import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import androidx.core.net.ConnectivityManagerCompat
import pan.alexander.tordnscrypt.modules.ModulesStatus
-import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker.getConnectivityManager
import pan.alexander.tordnscrypt.utils.enums.OperationMode
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
-import java.util.SortedMap
import java.util.TreeMap
private const val DEFAULT_MTU = 1400
@@ -78,6 +76,7 @@ object NetworkChecker {
private fun hasActiveTransport(capabilities: NetworkCapabilities): Boolean =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) &&
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && isCellularInternetMayBeAvailable(capabilities)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
@@ -123,6 +122,7 @@ object NetworkChecker {
private fun hasCellularTransport(capabilities: NetworkCapabilities): Boolean =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) &&
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && isCellularInternetMayBeAvailable(capabilities)
@JvmStatic
@@ -171,6 +171,7 @@ object NetworkChecker {
private fun hasRoamingTransport(capabilities: NetworkCapabilities): Boolean =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) &&
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && isCellularInternetMayBeAvailable(capabilities)
&& !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
@JvmStatic
@@ -310,6 +311,29 @@ object NetworkChecker {
false
}
+ private fun getVpnInterfaceName(context: Context): String = try {
+ val connectivityManager = context.getConnectivityManager()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && connectivityManager != null) {
+ connectivityManager.allNetworks.let {
+ for (network in it) {
+ val networkCapabilities =
+ connectivityManager.getNetworkCapabilities(network)
+ if (networkCapabilities != null && hasVpnTransport(networkCapabilities)) {
+ return connectivityManager.getLinkProperties(network)?.interfaceName ?: ""
+ }
+ }
+ return ""
+ }
+
+ } else {
+ ""
+ }
+ } catch (e: Exception) {
+ loge("NetworkChecker getVpnInterfaceName", e)
+ ""
+ }
+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun hasVpnTransport(capabilities: NetworkCapabilities): Boolean =
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
@@ -376,7 +400,8 @@ object NetworkChecker {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
networks[2] = network
- capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
+ capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && isCellularInternetMayBeAvailable(capabilities) ->
networks[3] = network
}
} else if (capabilities != null
@@ -389,7 +414,8 @@ object NetworkChecker {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
networks[5] = network
- capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
+ capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && isCellularInternetMayBeAvailable(capabilities) ->
networks[6] = network
}
}
@@ -409,6 +435,47 @@ object NetworkChecker {
return networks.values.toTypedArray()
}
+ //This is required because the cellular interface may only be available for ims
+ private fun isCellularInternetMayBeAvailable(capabilities: NetworkCapabilities): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }
+ return true
+ }
+
+ @JvmStatic
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun getCurrentActiveInterface(context: Context): String = try {
+ val connectivityManager = context.getConnectivityManager()
+ if (connectivityManager != null) {
+ getVpnInterfaceName(context).ifEmpty {
+ connectivityManager.activeNetwork?.let {
+ connectivityManager.getLinkProperties(it)?.interfaceName
+ } ?: ""
+ }
+ } else {
+ ""
+ }
+ } catch (e: Exception) {
+ loge("NetworkChecker getCurrentActiveInterface", e)
+ ""
+ }
+
+ @JvmStatic
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun getUnderlyingVpnActiveInterface(context: Context): String = try {
+ val connectivityManager = context.getConnectivityManager()
+ if (connectivityManager != null) {
+ connectivityManager.activeNetwork?.let {
+ connectivityManager.getLinkProperties(it)?.interfaceName
+ } ?: ""
+ } else {
+ ""
+ }
+ } catch (e: Exception) {
+ loge("NetworkChecker getUnderlyingVpnActiveInterface", e)
+ ""
+ }
private fun Context.getConnectivityManager(): ConnectivityManager? =
getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt
index 8f1517789..a6c4aa44b 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/mode/AppModeManager.kt
@@ -100,6 +100,7 @@ class AppModeManager @Inject constructor(
//This stop iptables adaptation
modulesStatus.mode = OperationMode.PROXY_MODE
+ modulesStatus.setFirewallState(ModuleState.STOPPED, preferenceRepository.get())
if (modulesStatus.isRootAvailable && operationMode == OperationMode.ROOT_MODE) {
val iptablesRules: IptablesRules = ModulesIptablesRules(context)
val commands = iptablesRules.clearAll()
@@ -137,9 +138,11 @@ class AppModeManager @Inject constructor(
val dnsCryptState: ModuleState = modulesStatus.dnsCryptState
val torState: ModuleState = modulesStatus.torState
val itpdState: ModuleState = modulesStatus.itpdState
+ val firewallState: ModuleState = modulesStatus.firewallState
if (dnsCryptState != ModuleState.STOPPED
|| torState != ModuleState.STOPPED
|| itpdState != ModuleState.STOPPED
+ || firewallState != ModuleState.STOPPED
) {
if (modulesStatus.isUseModulesWithRoot) {
Toast.makeText(context, "Stop modules...", Toast.LENGTH_LONG).show()
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java
index 6e2a27a25..5ae1a34bc 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/preferences/PreferenceKeys.java
@@ -87,6 +87,12 @@ public interface PreferenceKeys {
String GP_DATA = "gpData";
String GP_SIGNATURE = "gpSign";
+ String REMOTE_BLACKLIST_URL = "remote_blacklist_url";
+ String REMOTE_WHITELIST_URL = "remote_whitelist_url";
+ String REMOTE_IP_BLACKLIST_URL = "remote_ip_blacklist_url";
+ String REMOTE_FORWARDING_URL = "remote_forwarding_url";
+ String REMOTE_CLOAKING_URL = "remote_cloaking_url";
+
//VPN
String VPN_SERVICE_ENABLED = "VPNServiceEnabled";
@@ -117,6 +123,7 @@ public interface PreferenceKeys {
String MULTI_USER_SUPPORT = "pref_common_multi_user";
String REFRESH_RULES = "swRefreshRules";
String KILL_SWITCH = "swKillSwitch";
+ String ALWAYS_ON_VPN = "always_on_vpn";
String USE_IPTABLES = "pref_common_use_iptables";
String WAIT_IPTABLES = "pref_common_wait_iptables";
String REMOTE_CONTROL = "pref_common_shell_control";
@@ -140,6 +147,10 @@ public interface PreferenceKeys {
String DNSCRYPT_OUTBOUND_PROXY = "Enable proxy";
+ String DNSCRYPT_SERVERS_REFRESH_DELAY = "refresh_delay";
+ String DNSCRYPT_RELAYS_REFRESH_DELAY = "refresh_delay_relays";
+ String DNSCRYPT_RULES_REFRESH_DELAY = "refresh_delay_rules";
+
//Tor Settings
String TOR_DNS_PORT = "DNSPort";
String TOR_SOCKS_PORT = "SOCKSPort";
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt
index 7894bc177..005c4b33d 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/utils/privatedns/PrivateDnsProxyManager.kt
@@ -39,19 +39,27 @@ import pan.alexander.tordnscrypt.R
import pan.alexander.tordnscrypt.modules.ModulesStatus
import pan.alexander.tordnscrypt.utils.Utils.areNotificationsNotAllowed
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker
+import pan.alexander.tordnscrypt.utils.enums.ModuleState
import pan.alexander.tordnscrypt.utils.enums.OperationMode
import pan.alexander.tordnscrypt.utils.logger.Logger.loge
import pan.alexander.tordnscrypt.utils.logger.Logger.logi
import pan.alexander.tordnscrypt.vpn.VpnUtils
+import pan.alexander.tordnscrypt.vpn.VpnUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC
+import pan.alexander.tordnscrypt.vpn.VpnUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME
const val DISABLE_PRIVATE_DNS_NOTIFICATION = 167
const val DISABLE_PROXY_NOTIFICATION = 168
object PrivateDnsProxyManager {
@RequiresApi(Build.VERSION_CODES.P)
- fun checkPrivateDNSAndProxy(context: Context, linkProperties: LinkProperties?) {
+ fun checkPrivateDNSAndProxy(
+ context: Context,
+ linkProperties: LinkProperties?,
+ ignoreSystemDns: Boolean
+ ) {
try {
- if (ModulesStatus.getInstance().mode == OperationMode.PROXY_MODE) {
+ val modulesStatus = ModulesStatus.getInstance()
+ if (modulesStatus.mode == OperationMode.PROXY_MODE) {
return
}
@@ -68,7 +76,14 @@ object PrivateDnsProxyManager {
// localLinkProperties.privateDnsServerName == null - Opportunistic mode ("Automatic")
- if (VpnUtils.isPrivateDns(context) || localLinkProperties?.isPrivateDnsActive == true) {
+ val privateDnsMode = VpnUtils.getPrivateDnsMode(context)
+ if ((modulesStatus.dnsCryptState == ModuleState.RUNNING
+ || modulesStatus.torState == ModuleState.RUNNING)
+ && (privateDnsMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME
+ || privateDnsMode == PRIVATE_DNS_MODE_OPPORTUNISTIC && !ignoreSystemDns
+ || localLinkProperties?.isPrivateDnsActive == true
+ && (localLinkProperties.privateDnsServerName != null || !ignoreSystemDns))
+ ) {
sendNotification(
context,
context.getString(R.string.app_name),
@@ -77,7 +92,10 @@ object PrivateDnsProxyManager {
)
}
- if (localLinkProperties?.httpProxy != null) {
+ if ((modulesStatus.dnsCryptState == ModuleState.RUNNING
+ || modulesStatus.torState == ModuleState.RUNNING)
+ && localLinkProperties?.httpProxy != null
+ ) {
if (NetworkChecker.isWifiActive(context)) {
sendNotification(
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java
index e0bcf9210..9ee3841ba 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/VpnUtils.java
@@ -19,6 +19,7 @@
package pan.alexander.tordnscrypt.vpn;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -28,6 +29,7 @@
import android.net.Network;
import android.os.Build;
import android.provider.Settings;
+import android.text.TextUtils;
import androidx.annotation.Keep;
@@ -245,13 +247,31 @@ public static boolean canFilter() {
}
}
- public static boolean isPrivateDns(Context context) {
- String dns_mode = Settings.Global.getString(context.getContentResolver(), "private_dns_mode");
- logi("Private DNS mode=" + dns_mode);
- if (dns_mode == null) {
- dns_mode = "off";
+ public static final int PRIVATE_DNS_MODE_OFF = 1;
+ public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+ public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+ public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+
+ public static int getPrivateDnsMode(Context context) {
+ try {
+ final ContentResolver cr = context.getContentResolver();
+ String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
+ return getPrivateDnsModeAsInt(mode);
+ } catch (Exception e) {
+ loge("VpnUtils getPrivateDnsMode", e);
}
- return (!"off".equals(dns_mode));
+ return PRIVATE_DNS_MODE_OFF;
+ }
+ private static int getPrivateDnsModeAsInt(String mode) {
+ if (TextUtils.isEmpty(mode))
+ return PRIVATE_DNS_MODE_OFF;
+ return switch (mode) {
+ case "hostname" -> PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+ case "opportunistic" -> PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ default -> PRIVATE_DNS_MODE_OFF;
+ };
}
public static boolean isIpInSubnetOld(final String ip, final String network) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java
index 0d00df68c..d53e12ec6 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java
@@ -64,6 +64,7 @@
import pan.alexander.tordnscrypt.modules.UsageStatistics;
import pan.alexander.tordnscrypt.settings.PathVars;
import pan.alexander.tordnscrypt.utils.Utils;
+import pan.alexander.tordnscrypt.utils.enums.OperationMode;
import pan.alexander.tordnscrypt.utils.enums.VPNCommand;
import pan.alexander.tordnscrypt.utils.executors.CoroutineExecutor;
import pan.alexander.tordnscrypt.vpn.Allowed;
@@ -97,6 +98,7 @@
import static pan.alexander.tordnscrypt.utils.bootcomplete.BootCompleteManager.ALWAYS_ON_VPN;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RESTARTING;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logw;
@@ -144,6 +146,7 @@ public class ServiceVPN extends VpnService implements OnInternetConnectionChecke
public Lazy vpnRulesHolder;
NotificationManager notificationManager;
+ private ModulesServiceNotificationManager serviceNotificationManager;
private static final Object jni_lock = new Object();
private static volatile long jni_context = 0;
private volatile long service_jni_context = 0;
@@ -613,13 +616,14 @@ public void onCreate() {
message = UsageStatistics.getSavedMessage();
}
- ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager(
+ serviceNotificationManager = ModulesServiceNotificationManager.getManager(this);
+ serviceNotificationManager.createNotificationChannel(this);
+ serviceNotificationManager.sendNotification(
this,
- notificationManager,
+ title,
+ message,
UsageStatistics.getStartTime()
);
- notification.createNotificationChannel();
- notification.sendNotification(title, message);
}
App.getInstance().getSubcomponentsManager().modulesServiceSubcomponent().inject(this);
@@ -671,12 +675,16 @@ public int onStartCommand(Intent intent, int flags, int startId) {
message = UsageStatistics.getSavedMessage();
}
- ModulesServiceNotificationManager notification = new ModulesServiceNotificationManager(
- this,
- notificationManager,
- UsageStatistics.getStartTime()
- );
- notification.sendNotification(title, message);
+ if (serviceNotificationManager == null) {
+ serviceNotificationManager = ModulesServiceNotificationManager
+ .getManager(this);
+ serviceNotificationManager.sendNotification(
+ this,
+ title,
+ message,
+ UsageStatistics.getStartTime()
+ );
+ }
}
logi("VPN Received " + intent);
@@ -780,6 +788,12 @@ public void onDestroy() {
connectionCheckerInteractor.get().removeListener(this);
handler.get().removeCallbacksAndMessages(null);
+ ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if (modulesStatus.getMode() == OperationMode.VPN_MODE
+ || modulesStatus.getMode() == OperationMode.PROXY_MODE) {
+ ModulesStatus.getInstance().setFirewallState(STOPPED, preferenceRepository.get());
+ }
+
final long localJniContext = service_jni_context;
executor.get().submit("ServiceVPN onDestroy", () -> {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java
index aaa3e078c..8e508883a 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHandler.java
@@ -54,11 +54,15 @@
import pan.alexander.tordnscrypt.settings.PathVars;
import pan.alexander.tordnscrypt.utils.connectionchecker.NetworkChecker;
import pan.alexander.tordnscrypt.utils.enums.ModuleState;
+import pan.alexander.tordnscrypt.utils.enums.OperationMode;
import pan.alexander.tordnscrypt.utils.enums.VPNCommand;
import pan.alexander.tordnscrypt.vpn.Rule;
import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
import static pan.alexander.tordnscrypt.modules.ModulesService.DEFAULT_NOTIFICATION_ID;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
import static pan.alexander.tordnscrypt.utils.logger.Logger.logw;
@@ -205,6 +209,14 @@ private void start() {
}
serviceVPN.startNative(serviceVPN.vpn, listAllowed);
+
+ ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if (modulesStatus.getMode() == OperationMode.VPN_MODE
+ && serviceVPN.vpnPreferences.getFirewallEnabled()) {
+ modulesStatus.setFirewallState(RUNNING, preferenceRepository.get());
+ } else {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
}
}
@@ -312,6 +324,16 @@ private void reload() {
serviceVPN.reloading = false;
+ if (modulesStatus.getMode() == OperationMode.VPN_MODE) {
+ if (modulesStatus.getFirewallState() == STARTING) {
+ modulesStatus.setFirewallState(RUNNING, preferenceRepository.get());
+ } else if (modulesStatus.getFirewallState() == STOPPING) {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
+ } else {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
+
if (defaultSharedPreferences.get().getBoolean(ARP_SPOOFING_DETECTION, false)) {
try {
ArpScanner.getArpComponent().get().reset(
@@ -445,6 +467,10 @@ private void stopServiceVPN() {
serviceVPN.stopSelf();
ModulesStatus modulesStatus = ModulesStatus.getInstance();
+ if (modulesStatus.getMode() == OperationMode.VPN_MODE
+ || modulesStatus.getMode() == OperationMode.PROXY_MODE) {
+ modulesStatus.setFirewallState(STOPPED, preferenceRepository.get());
+ }
ModuleState dnsCryptState = modulesStatus.getDnsCryptState();
ModuleState torState = modulesStatus.getTorState();
ModuleState itpdState = modulesStatus.getItpdState();
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java
index ff06d5208..66a0a9050 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPNHelper.java
@@ -35,6 +35,7 @@
import pan.alexander.tordnscrypt.utils.enums.OperationMode;
import pan.alexander.tordnscrypt.utils.enums.VPNCommand;
+import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STARTING;
import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.VPN_SERVICE_ENABLED;
import static pan.alexander.tordnscrypt.utils.enums.ModuleState.RUNNING;
@@ -59,6 +60,7 @@ public static void reload(String reason, Context context) {
OperationMode operationMode = modulesStatus.getMode();
ModuleState dnsCryptState = modulesStatus.getDnsCryptState();
ModuleState torState = modulesStatus.getTorState();
+ ModuleState firewallState = modulesStatus.getFirewallState();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean vpnServiceEnabled = prefs.getBoolean(VPN_SERVICE_ENABLED, false);
@@ -67,7 +69,8 @@ public static void reload(String reason, Context context) {
if (((operationMode == VPN_MODE) || fixTTL)
&& vpnServiceEnabled
- && (dnsCryptState == RUNNING || torState == RUNNING)) {
+ && (dnsCryptState == RUNNING || torState == RUNNING
+ || firewallState == RUNNING || firewallState == STARTING)) {
Intent intent = new Intent(context, ServiceVPN.class);
intent.putExtra(EXTRA_COMMAND, VPNCommand.RELOAD);
intent.putExtra(EXTRA_REASON, reason);
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt
index 4342cc1ab..771895a32 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnPreferenceHolder.kt
@@ -83,7 +83,7 @@ class VpnPreferenceHolder @Inject constructor(
&& proxyAddress.isNotBlank()
&& proxyPort != 0
- val torIsolateUid = defaultPreferences.getBoolean(TOR_ISOLATE_UID, false)
+ val torIsolateUid = defaultPreferences.getBoolean(TOR_ISOLATE_UID, true)
val torDNSPort = pathVars.torDNSPort.let {
if (it.matches(Regex(NUMBER_REGEX)) && it.toLong() <= MAX_PORT_NUMBER) {
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java
deleted file mode 100644
index 91fa0debf..000000000
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnReceiver.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- This file is part of InviZible Pro.
-
- InviZible Pro 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.
-
- InviZible Pro 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 InviZible Pro. If not, see .
-
- Copyright 2019-2024 by Garmatin Oleksandr invizible.soft@gmail.com
- */
-
-package pan.alexander.tordnscrypt.vpn.service;
-
-import static pan.alexander.tordnscrypt.di.SharedPreferencesModule.DEFAULT_PREFERENCES_NAME;
-import static pan.alexander.tordnscrypt.utils.logger.Logger.loge;
-import static pan.alexander.tordnscrypt.utils.logger.Logger.logi;
-import static pan.alexander.tordnscrypt.utils.preferences.PreferenceKeys.REFRESH_RULES;
-import static pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper.reload;
-
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.Build;
-import android.os.PowerManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.net.InetAddress;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import dagger.Lazy;
-import pan.alexander.tordnscrypt.domain.connection_checker.ConnectionCheckerInteractor;
-import pan.alexander.tordnscrypt.settings.firewall.FirewallNotification;
-import pan.alexander.tordnscrypt.utils.privatedns.PrivateDnsProxyManager;
-
-public class VpnReceiver {
-
- private final Lazy connectionCheckerInteractor;
- private final Lazy defaultPreferences;
-
- private boolean registeredIdleState = false;
- private boolean registeredPackageChanged = false;
- private boolean registeredConnectivityChanged = false;
- private Object networkCallback = null;
- private FirewallNotification firewallNotificationReceiver;
-
- @Inject
- public VpnReceiver(
- Lazy connectionCheckerInteractor,
- @Named(DEFAULT_PREFERENCES_NAME)
- Lazy defaultPreferences
- ) {
- this.connectionCheckerInteractor = connectionCheckerInteractor;
- this.defaultPreferences = defaultPreferences;
- }
-
- @RequiresApi(api = Build.VERSION_CODES.M)
- void listenIdleStateChanged(ServiceVPN vpn) {
- IntentFilter ifIdle = new IntentFilter();
- ifIdle.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
- vpn.registerReceiver(idleStateReceiver, ifIdle);
- registeredIdleState = true;
- }
-
- void unlistenIdleStateChanged(ServiceVPN vpn) {
- if (registeredIdleState) {
- vpn.unregisterReceiver(idleStateReceiver);
- registeredIdleState = false;
- }
- }
-
- void listenAddRemoveApp(ServiceVPN vpn) {
- IntentFilter ifPackage = new IntentFilter();
- ifPackage.addAction(Intent.ACTION_PACKAGE_ADDED);
- ifPackage.addAction(Intent.ACTION_PACKAGE_REMOVED);
- ifPackage.addDataScheme("package");
- vpn.registerReceiver(packageChangedReceiver, ifPackage);
-
- registerFirewallReceiver(vpn);
-
- registeredPackageChanged = true;
- }
-
- void unlistenAddRemoveApp(ServiceVPN vpn) {
- if (registeredPackageChanged) {
- vpn.unregisterReceiver(packageChangedReceiver);
- registeredPackageChanged = false;
-
- unregisterFirewallReceiver(vpn);
- }
- }
-
- private void registerFirewallReceiver(ServiceVPN vpn) {
- firewallNotificationReceiver = FirewallNotification.Companion.registerFirewallReceiver(vpn);
- }
-
- private void unregisterFirewallReceiver(ServiceVPN vpn) {
- FirewallNotification.Companion.unregisterFirewallReceiver(vpn, firewallNotificationReceiver);
- }
-
- private final BroadcastReceiver idleStateReceiver = new BroadcastReceiver() {
- @Override
- @TargetApi(Build.VERSION_CODES.M)
- public void onReceive(Context context, Intent intent) {
- logi("VPN Received " + intent);
-
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (pm != null) {
- logi("VPN device idle=" + pm.isDeviceIdleMode());
- }
-
- // Reload rules when coming from idle mode
- if (pm != null && !pm.isDeviceIdleMode()) {
- setInternetAvailable(false);
- reload("VPN idle state changed", context);
- }
- }
- };
-
- private final BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Filter VPN connectivity changes
- int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY);
- if (networkType == ConnectivityManager.TYPE_VPN)
- return;
-
- // Reload rules
- logi("VPN Received " + intent);
- setInternetAvailable(false);
- reload("Connectivity changed", context);
- }
- };
-
- private final BroadcastReceiver packageChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- logi("VPN Received " + intent);
-
- try {
- if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
- reload("VPN Package added", context);
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
- reload("VPN Package deleted", context);
- }
- } catch (Throwable ex) {
- loge(ex.toString() + "\n" + Log.getStackTraceString(ex));
- }
- }
- };
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- void listenNetworkChanges(ServiceVPN vpn) {
- // Listen for network changes
- logi("VPN Starting listening to network changes");
- ConnectivityManager cm = (ConnectivityManager) vpn.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkRequest.Builder builder = new NetworkRequest.Builder();
- /*builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
- }*/
-
- ConnectivityManager.NetworkCallback nc = new ConnectivityManager.NetworkCallback() {
- private Boolean last_connected = null;
- private List last_dns = null;
- private int last_network = 0;
-
- @Override
- public void onAvailable(@NonNull Network network) {
- logi("VPN Available network=" + network);
- connectionCheckerInteractor.get().checkNetworkConnection();
- last_connected = isNetworkAvailable();
-
- if (!last_connected) {
- last_connected = true;
- setInternetAvailable(true);
- }
-
- reload("Network available", vpn);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && last_network != network.hashCode()) {
- PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy(
- vpn, null
- );
- }
-
- last_network = network.hashCode();
- }
-
- @Override
- public void onLinkPropertiesChanged(@NonNull Network network, LinkProperties linkProperties) {
- // Make sure the right DNS servers are being used
- List dns = linkProperties.getDnsServers();
- SharedPreferences prefs = defaultPreferences.get();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- ? !same(last_dns, dns)
- : prefs.getBoolean(REFRESH_RULES, false)) {
- logi("VPN Changed link properties=" + linkProperties +
- "DNS cur=" + TextUtils.join(",", dns) +
- "DNS prv=" + (last_dns == null ? null : TextUtils.join(",", last_dns)));
- last_dns = dns;
- logi("VPN Changed link properties=" + linkProperties);
-
- if (network.hashCode() != last_network) {
- last_network = network.hashCode();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- PrivateDnsProxyManager.INSTANCE.checkPrivateDNSAndProxy(
- vpn, linkProperties
- );
- }
-
- setInternetAvailable(false);
- reload("VPN Link properties changed", vpn);
- }
- }
- }
-
- @Override
- public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
- connectionCheckerInteractor.get().checkNetworkConnection();
- if (isNetworkAvailable() && (last_connected == null || !last_connected)) {
- last_connected = true;
- setInternetAvailable(false);
- reload("VPN Connected state changed", vpn);
- }
-
- last_network = network.hashCode();
-
- logi("VPN Changed capabilities=" + network);
- }
-
- @Override
- public void onLost(@NonNull Network network) {
- logi("VPN Lost network=" + network);
- connectionCheckerInteractor.get().checkNetworkConnection();
- last_connected = isNetworkAvailable();
-
- setInternetAvailable(false);
-
- reload("Network lost", vpn);
-
- last_network = 0;
- }
-
- boolean same(List last, List current) {
- if (last == null || current == null)
- return false;
- if (last.size() != current.size())
- return false;
-
- for (int i = 0; i < current.size(); i++)
- if (!last.get(i).equals(current.get(i)))
- return false;
-
- return true;
- }
- };
- if (cm != null) {
- cm.registerNetworkCallback(builder.build(), nc);
- networkCallback = nc;
- }
- }
-
- void unlistenNetworkChanges(ServiceVPN vpn) {
- if (networkCallback != null) {
- unregisterNetworkChanges(vpn);
- networkCallback = null;
- }
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void unregisterNetworkChanges(ServiceVPN vpn) {
- ConnectivityManager cm = (ConnectivityManager) vpn.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm != null) {
- cm.unregisterNetworkCallback((ConnectivityManager.NetworkCallback) networkCallback);
- }
- }
-
- void listenConnectivityChanges(ServiceVPN vpn) {
- // Listen for connectivity updates
- logi("VPN Starting listening to connectivity changes");
- IntentFilter ifConnectivity = new IntentFilter();
- ifConnectivity.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- vpn.registerReceiver(connectivityChangedReceiver, ifConnectivity);
- registeredConnectivityChanged = true;
- }
-
- void unlistenConnectivityChanges(ServiceVPN vpn) {
- if (registeredConnectivityChanged) {
- vpn.unregisterReceiver(connectivityChangedReceiver);
- registeredConnectivityChanged = false;
- }
- }
-
- private boolean isNetworkAvailable() {
- return connectionCheckerInteractor.get().getNetworkConnectionResult();
- }
-
- private void setInternetAvailable(boolean available) {
- connectionCheckerInteractor.get().setInternetConnectionResult(available);
- }
-}
diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java
index 55b86bb94..c5af713c9 100644
--- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java
+++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/VpnRulesHolder.java
@@ -130,6 +130,10 @@ public Allowed isAddressAllowed(ServiceVPN vpn, Packet packet) {
boolean fixTTLForPacket = isFixTTLForPacket(packet);
+ boolean dnsCryptIsRunning = modulesStatus.getDnsCryptState() == RUNNING
+ || modulesStatus.getDnsCryptState() == STARTING
+ || modulesStatus.getDnsCryptState() == RESTARTING;
+
lock.readLock().lock();
VpnPreferenceHolder vpnPreferences = vpn.vpnPreferences;
@@ -156,12 +160,14 @@ public Allowed isAddressAllowed(ServiceVPN vpn, Packet packet) {
} else if (!isSupported(packet.protocol)) {
logw("Protocol not supported " + packet);
} else if (packet.dport == DNS_OVER_TLS_PORT
- && vpnPreferences.getIgnoreSystemDNS()) {
+ && vpnPreferences.getIgnoreSystemDNS()
+ && (dnsCryptIsRunning || torIsRunning)) {
logw("Block DNS over TLS " + packet);
} else if (vpnDnsSet.contains(packet.daddr)
&& packet.dport != PLAINTEXT_DNS_PORT
&& vpnPreferences.getIgnoreSystemDNS()
- && packet.uid != vpnPreferences.getOwnUID()) {
+ && packet.uid != vpnPreferences.getOwnUID()
+ && (dnsCryptIsRunning || torIsRunning)) {
logw("Block DNS over HTTPS " + packet);
} else if (packet.uid == vpnPreferences.getOwnUID()
|| vpnPreferences.getCompatibilityMode()
@@ -449,6 +455,7 @@ void prepareForwarding() {
ModuleState dnsCryptState = modulesStatus.getDnsCryptState();
ModuleState torState = modulesStatus.getTorState();
ModuleState itpdState = modulesStatus.getItpdState();
+ ModuleState firewallState = modulesStatus.getFirewallState();
int ownUID = pathVars.getAppUid();
@@ -469,24 +476,40 @@ void prepareForwarding() {
//If Tor is ready and DNSCrypt is not, app will use Tor Exit node DNS in VPN mode
if (dnsCryptState == RUNNING && (dnsCryptReady || !systemDNSAllowed)) {
- addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
- addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
-
+ forwardDnsToDnsCrypt(dnsCryptPort, ownUID);
if (itpdState == RUNNING) {
- addForwardAddressRule(17, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID);
- addForwardAddressRule(6, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID);
+ forwardAddressToITPD(itpdHttpPort, ownUID);
}
} else if (torState == RUNNING && (torReady || !systemDNSAllowed)) {
- addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID);
- addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID);
+ forwardDnsToTor(torDNSPort, ownUID);
+ } else if (dnsCryptState != STOPPED) {
+ forwardDnsToDnsCrypt(dnsCryptPort, ownUID);
+ } else if (torState != STOPPED) {
+ forwardDnsToTor(torDNSPort, ownUID);
+ } else if (firewallState == STARTING || firewallState == RUNNING) {
+ logi("Firewall only operation");
} else {
- addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
- addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
+ forwardDnsToDnsCrypt(dnsCryptPort, ownUID);
}
lock.writeLock().unlock();
}
+ private void forwardDnsToDnsCrypt(int dnsCryptPort, int ownUID) {
+ addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
+ addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, dnsCryptPort, ownUID);
+ }
+
+ private void forwardDnsToTor(int torDNSPort, int ownUID) {
+ addForwardPortRule(17, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID);
+ addForwardPortRule(6, PLAINTEXT_DNS_PORT, LOOPBACK_ADDRESS, torDNSPort, ownUID);
+ }
+
+ private void forwardAddressToITPD(int itpdHttpPort, int ownUID) {
+ addForwardAddressRule(17, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID);
+ addForwardAddressRule(6, "10.191.0.1", LOOPBACK_ADDRESS, itpdHttpPort, ownUID);
+ }
+
@SuppressWarnings("SameParameterValue")
private void addForwardPortRule(int protocol, int dport, String raddr, int rport, int ruid) {
Forward fwd = new Forward();
diff --git a/tordnscrypt/src/main/jni/invizible/invizible.c b/tordnscrypt/src/main/jni/invizible/invizible.c
index 638261041..708a7651a 100644
--- a/tordnscrypt/src/main/jni/invizible/invizible.c
+++ b/tordnscrypt/src/main/jni/invizible/invizible.c
@@ -205,6 +205,11 @@ Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1stop(
JNIEXPORT void JNICALL
Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1clear(
JNIEnv *env, jobject instance, jlong context) {
+
+ if (context == 0) {
+ return;
+ }
+
struct context *ctx = (struct context *) context;
clear(ctx);
}
@@ -281,6 +286,11 @@ Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1socks5_1for_1proxy(JN
JNIEXPORT void JNICALL
Java_pan_alexander_tordnscrypt_vpn_service_ServiceVPN_jni_1done(
JNIEnv *env, jobject instance, jlong context) {
+
+ if (context == 0) {
+ return;
+ }
+
struct context *ctx = (struct context *) context;
log_android(ANDROID_LOG_INFO, "Done");
diff --git a/tordnscrypt/src/main/jni/invizible/udp.c b/tordnscrypt/src/main/jni/invizible/udp.c
index 4c9ae8329..0ab271b7b 100644
--- a/tordnscrypt/src/main/jni/invizible/udp.c
+++ b/tordnscrypt/src/main/jni/invizible/udp.c
@@ -285,7 +285,7 @@ jboolean handle_udp(const struct arguments *args,
return 0;
}
- if (ntohs(udphdr->dest) == 53 && redirect == NULL && uid != own_uid) {
+ if (ntohs(udphdr->dest) == 53 && redirect == NULL && uid != own_uid && args->fwd53) {
log_android(
ANDROID_LOG_ERROR, "Direct DNS connection for %s/%u to %s/%u uid %uid not allowed",
source, ntohs(udphdr->source), dest, ntohs(udphdr->dest), uid
diff --git a/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml b/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml
new file mode 100644
index 000000000..581ef09b3
--- /dev/null
+++ b/tordnscrypt/src/main/res/drawable/ic_firewall_active_menu.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml b/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml
new file mode 100644
index 000000000..5b16bf79e
--- /dev/null
+++ b/tordnscrypt/src/main/res/drawable/ic_firewall_menu.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/drawable/ic_refresh.xml b/tordnscrypt/src/main/res/drawable/ic_refresh.xml
new file mode 100644
index 000000000..43df87d0b
--- /dev/null
+++ b/tordnscrypt/src/main/res/drawable/ic_refresh.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml b/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml
new file mode 100644
index 000000000..90207d579
--- /dev/null
+++ b/tordnscrypt/src/main/res/drawable/ic_vpn_key.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml b/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml
deleted file mode 100644
index 5d6835807..000000000
--- a/tordnscrypt/src/main/res/drawable/ic_vpn_key_white_24dp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml b/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml
index 61ef9b9f1..f8ce53446 100644
--- a/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml
+++ b/tordnscrypt/src/main/res/layout-land/fragment_dnscrypt_run.xml
@@ -31,13 +31,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/status_text_margin"
- android:fontFamily="serif"
android:singleLine="true"
android:text="@string/tvDNSStop"
android:textAlignment="center"
android:textColor="@color/textModuleStatusColorStopped"
- android:textSize="@dimen/status_text_size"
- android:typeface="monospace" />
+ android:textSize="@dimen/status_text_size" />
+ android:textSize="@dimen/status_text_size" />
+ android:textSize="@dimen/status_text_size" />
+ android:layout_height="150dp"
+ android:background="@color/ic_launcher_background">
+ app:srcCompat="@drawable/gplv3"
+ app:tint="@color/colorWhite" />
+ android:textAlignment="viewEnd" />
+ android:textStyle="bold" />
+ android:textAlignment="viewEnd" />
+ android:textStyle="bold" />
@@ -109,8 +109,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/about_build_ver"
- android:textAlignment="viewEnd"
- android:typeface="serif" />
+ android:textAlignment="viewEnd" />
+ android:textStyle="bold" />
@@ -138,8 +137,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/about_proc_ver"
- android:textAlignment="viewEnd"
- android:typeface="serif" />
+ android:textAlignment="viewEnd" />
+ android:textStyle="bold" />
+ android:textAlignment="viewEnd" />
+ android:textStyle="bold" />
+ android:textAlignment="center" />
+ android:textColor="@color/ic_launcher_background" />
+ android:textStyle="bold" />
+ android:textStyle="bold" />
+ android:text="@string/privacy_policy" />
+ android:text="@string/about_own_licence" />
+ android:textStyle="bold" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
+ android:textColor="@color/colorAccent" />
diff --git a/tordnscrypt/src/main/res/layout/activity_help.xml b/tordnscrypt/src/main/res/layout/activity_help.xml
index b39246a4e..6b3561424 100644
--- a/tordnscrypt/src/main/res/layout/activity_help.xml
+++ b/tordnscrypt/src/main/res/layout/activity_help.xml
@@ -66,10 +66,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_start_selector"
+ android:foreground="?attr/selectableItemBackgroundBorderless"
android:minHeight="45dp"
android:text="@string/help_activity_save_logs"
- android:textColor="@color/buttonTextColor"
- android:typeface="serif" />
+ android:textColor="@color/buttonTextColor" />
+ android:textAlignment="center" />
+ android:textAlignment="center" />
-
\ No newline at end of file
+ android:textAlignment="center" />
+
diff --git a/tordnscrypt/src/main/res/layout/agreement_layout.xml b/tordnscrypt/src/main/res/layout/agreement_layout.xml
index 5e797713f..59e940560 100644
--- a/tordnscrypt/src/main/res/layout/agreement_layout.xml
+++ b/tordnscrypt/src/main/res/layout/agreement_layout.xml
@@ -41,8 +41,7 @@
android:autoLink="all"
android:linksClickable="true"
android:padding="8dp"
- android:text="@string/agreement_text"
- android:typeface="serif" />
+ android:text="@string/agreement_text" />
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml b/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml
index f9d63144e..a054dd048 100644
--- a/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml
+++ b/tordnscrypt/src/main/res/layout/edit_text_for_dialog.xml
@@ -12,6 +12,5 @@
android:ems="10"
android:imeOptions="actionDone"
android:inputType="text"
- android:textAlignment="center"
- android:typeface="serif" />
-
\ No newline at end of file
+ android:textAlignment="center" />
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_backup.xml b/tordnscrypt/src/main/res/layout/fragment_backup.xml
index ea9af167a..96c661f70 100644
--- a/tordnscrypt/src/main/res/layout/fragment_backup.xml
+++ b/tordnscrypt/src/main/res/layout/fragment_backup.xml
@@ -14,7 +14,7 @@
+ android:orientation="vertical">
+ android:textColor="@color/buttonTextColor" />
+ android:textColor="@color/buttonTextColor" />
+ android:textColor="@color/buttonTextColor" />
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_config_editor.xml b/tordnscrypt/src/main/res/layout/fragment_config_editor.xml
index cdd275cf5..8073fa3bd 100644
--- a/tordnscrypt/src/main/res/layout/fragment_config_editor.xml
+++ b/tordnscrypt/src/main/res/layout/fragment_config_editor.xml
@@ -23,7 +23,6 @@
android:ems="10"
android:focusable="true"
android:focusableInTouchMode="true"
- android:fontFamily="serif"
android:gravity="start"
android:imeOptions="actionDone"
android:inputType="textLongMessage|textMultiLine|textNoSuggestions|text"
@@ -33,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml b/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml
new file mode 100644
index 000000000..0fcbe4a24
--- /dev/null
+++ b/tordnscrypt/src/main/res/layout/fragment_dns_rule.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml b/tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml
similarity index 94%
rename from tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml
rename to tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml
index aba372d12..7c061d053 100644
--- a/tordnscrypt/src/main/res/layout/fragment_show_rules_recycle.xml
+++ b/tordnscrypt/src/main/res/layout/fragment_itpd_subscription.xml
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- tools:context=".settings.show_rules.ShowRulesRecycleFrag">
+ tools:context=".settings.itpd_settings.ITPDSubscriptionsFragment">
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml b/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml
index 681302e42..8c486f2ca 100644
--- a/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml
+++ b/tordnscrypt/src/main/res/layout/fragment_preferences_tor_bridges.xml
@@ -21,134 +21,137 @@
-
-
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:checked="true"
+ android:fontFamily="sans-serif-thin"
+ android:text="@string/pref_fast_use_tor_bridges_no"
+ android:textSize="15sp"
+ android:textStyle="bold" />
+
+
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:dropDownWidth="wrap_content"
+ android:entries="@array/tor_bridges_default_obfs"
+ android:spinnerMode="dialog" />
+
+
+
+
+
-
-
-
+
+
+ android:layout_weight="3"
+ android:entries="@array/tor_bridges_own_obfs"
+ android:spinnerMode="dialog" />
-
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/layout/fragment_show_log.xml b/tordnscrypt/src/main/res/layout/fragment_show_log.xml
index f38fef43e..5f1c02de3 100644
--- a/tordnscrypt/src/main/res/layout/fragment_show_log.xml
+++ b/tordnscrypt/src/main/res/layout/fragment_show_log.xml
@@ -31,7 +31,7 @@
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/fragment_file_log_text_size"
- android:typeface="serif" />
+ tools:text="@tools:sample/lorem/random" />
@@ -45,10 +45,9 @@
android:adjustViewBounds="true"
android:cropToPadding="true"
android:fitsSystemWindows="false"
- app:srcCompat="@drawable/ic_clear_white_24dp"
app:backgroundTint="@color/buttonColor"
app:fabSize="auto"
- app:rippleColor="@color/colorAccent" />
+ app:rippleColor="@color/colorAccent"
+ app:srcCompat="@drawable/ic_clear_white_24dp" />
-
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/layout/item_bridge.xml b/tordnscrypt/src/main/res/layout/item_bridge.xml
index f5c5c4206..c7ff2dd77 100644
--- a/tordnscrypt/src/main/res/layout/item_bridge.xml
+++ b/tordnscrypt/src/main/res/layout/item_bridge.xml
@@ -29,12 +29,13 @@
android:id="@+id/tvBridgeCountry"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:fontFamily="sans-serif-thin"
+ android:gravity="center"
android:paddingStart="8dp"
android:paddingEnd="8dp"
- android:fontFamily="serif"
- android:gravity="center"
android:textAlignment="gravity"
- tools:text = "EN"/>
+ android:textStyle="bold"
+ tools:text="EN" />
+ tools:text="obfs4 100.100.100.100:1234" />
+ android:textStyle="bold"
+ tools:text="100 ms" />
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/layout/item_dns_relay.xml b/tordnscrypt/src/main/res/layout/item_dns_relay.xml
index d35f35b76..2584238bc 100644
--- a/tordnscrypt/src/main/res/layout/item_dns_relay.xml
+++ b/tordnscrypt/src/main/res/layout/item_dns_relay.xml
@@ -34,11 +34,10 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
- android:fontFamily="serif"
+ android:fontFamily="sans-serif-medium"
android:singleLine="true"
android:textAlignment="center"
- android:textSize="16sp"
- android:textStyle="bold" />
+ android:textSize="16sp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tordnscrypt/src/main/res/layout/item_dns_server.xml b/tordnscrypt/src/main/res/layout/item_dns_server.xml
index 8b7ff3b3a..7521481b3 100644
--- a/tordnscrypt/src/main/res/layout/item_dns_server.xml
+++ b/tordnscrypt/src/main/res/layout/item_dns_server.xml
@@ -40,11 +40,11 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
- android:fontFamily="serif"
+ android:fontFamily="sans-serif-medium"
android:singleLine="true"
android:textAlignment="center"
android:textSize="16sp"
- android:textStyle="bold" />
+ tools:text="fgsfgsg.com" />
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="www.google.com" />
+ tools:visibility="visible" />
@@ -72,4 +74,4 @@
android:scaleType="fitCenter"
app:srcCompat="@android:drawable/ic_menu_delete" />
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/layout/item_tor_app.xml b/tordnscrypt/src/main/res/layout/item_tor_app.xml
index d09d7c57d..f71cdf0a8 100644
--- a/tordnscrypt/src/main/res/layout/item_tor_app.xml
+++ b/tordnscrypt/src/main/res/layout/item_tor_app.xml
@@ -61,7 +61,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- android:fontFamily="serif"
android:singleLine="false"
android:textAlignment="center"
android:textStyle="bold"
@@ -76,7 +75,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- android:fontFamily="serif"
android:singleLine="true"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/tordnscrypt/src/main/res/layout/item_tor_ips.xml b/tordnscrypt/src/main/res/layout/item_tor_ips.xml
index 594c1d14f..37bc5fb16 100644
--- a/tordnscrypt/src/main/res/layout/item_tor_ips.xml
+++ b/tordnscrypt/src/main/res/layout/item_tor_ips.xml
@@ -40,9 +40,8 @@
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="1"
- android:fontFamily="serif"
+ android:fontFamily="sans-serif-medium"
android:gravity="fill"
- android:textStyle="bold"
tools:ignore="NestedWeights" />
@@ -86,9 +85,9 @@
android:layout_gravity="bottom|end"
android:layout_marginEnd="3dp"
android:background="@drawable/button_item_selector"
+ android:clickable="true"
android:contentDescription="Delete"
android:focusable="true"
- android:clickable="true"
android:scaleType="fitCenter"
app:srcCompat="@android:drawable/ic_menu_delete" />
diff --git a/tordnscrypt/src/main/res/layout/main_fragment.xml b/tordnscrypt/src/main/res/layout/main_fragment.xml
index 68bba8936..6b0640636 100644
--- a/tordnscrypt/src/main/res/layout/main_fragment.xml
+++ b/tordnscrypt/src/main/res/layout/main_fragment.xml
@@ -126,7 +126,8 @@
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:gravity="center"
- android:text="@string/tvTorStop" />
+ android:text="@string/tvTorStop"
+ android:textSize="15sp" />
+ android:text="@string/tvDNSStop"
+ android:textSize="15sp" />
+ android:text="@string/tvITPDStop"
+ android:textSize="15sp" />
-
\ No newline at end of file
+
diff --git a/tordnscrypt/src/main/res/menu/main.xml b/tordnscrypt/src/main/res/menu/main.xml
index 1e654c5a8..75adefc7c 100644
--- a/tordnscrypt/src/main/res/menu/main.xml
+++ b/tordnscrypt/src/main/res/menu/main.xml
@@ -2,46 +2,52 @@
-
@@ -206,10 +202,6 @@
android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity"
android:targetPackage="@string/package_name" />
-
@@ -227,10 +219,6 @@
android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity"
android:targetPackage="@string/package_name" />
-
@@ -248,10 +236,6 @@
android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity"
android:targetPackage="@string/package_name" />
-
@@ -269,14 +253,23 @@
android:targetClass="pan.alexander.tordnscrypt.settings.SettingsActivity"
android:targetPackage="@string/package_name" />
-
+
+
+