Compare commits

...

102 Commits

Author SHA1 Message Date
2dust
6632ba3b6e Update libs.versions.toml 2025-08-25 18:39:00 +08:00
2dust
bd582f1d90 Update libs.versions.toml 2025-08-25 18:32:15 +08:00
Evgenii Pravda
f5f1e12565 Reasonable app sorting order (#4869) 2025-08-25 17:49:24 +08:00
2dust
207d6f4f8c Revert "Update build.yml"
This reverts commit fc0e60a097.
2025-08-17 11:15:48 +08:00
2dust
52e0a19826 up 1.10.19 2025-08-17 10:22:04 +08:00
2dust
fc0e60a097 Update build.yml 2025-08-17 09:41:41 +08:00
Hossin Asaadi
65d6b4aaa8 fix redirect infinite loop (#4857) 2025-08-17 09:27:22 +08:00
Hossin Asaadi
7b11755e7f IPv6 Unreachability Fallback for TLS Configs (#4846)
* fix unreachable ipv6 fallback

* add UseIP domainStrategy

* fix DNS query loop
2025-08-16 14:34:53 +08:00
fuilloi
2d0de4860c fix (#4855) 2025-08-16 14:06:35 +08:00
solokot
7406ef16ff Update Russian translation (#4845) 2025-08-14 17:33:24 +08:00
DHR60
a084b21d50 Improves intelligent selection toast and DNS routing (#4838)
* Improves intelligent selection toast and DNS routing

* rename
2025-08-13 16:59:22 +08:00
2dust
bf01fe2bdb up 1.10.18 2025-08-13 08:48:33 +08:00
Skh-web6982
519cc2a4b5 Bump actions/checkout from 4 to 5 (#4837)
Bump actions/checkout from 4 to 5
2025-08-13 08:38:18 +08:00
Tamim Hossain
c21653d40f Feat/add mtu in settings (#4836)
* Added MTU In Settings

Added MTU In Settings.
Closes  #4824

* Update SettingsViewModel.kt
2025-08-13 08:38:07 +08:00
solokot
1919c5e05f Update Russian translation (#4833) 2025-08-13 08:37:52 +08:00
2dust
8a17d93882 up 1.10.17 2025-08-12 18:04:41 +08:00
2dust
21ed008c7b up strings 2025-08-12 18:02:22 +08:00
Tamim Hossain
d83cfa28c2 Added MTU In Settings (#4828)
Added MTU In Settings.
Closes  #4824
2025-08-12 17:33:04 +08:00
2dust
d27e2091a7 up 1.10.16 2025-08-09 20:14:11 +08:00
solokot
c545678e47 Update Russian translation (#4823) 2025-08-09 19:48:31 +08:00
Hossein Abaspanah
05cba9b0fe Update Luri Bakhtiari translation (#4822) 2025-08-09 09:17:36 +08:00
Skh-web6982
b472aa0e6b Update Persian translation (#4821)
* Update Persian translation

* Update strings.xml
2025-08-09 09:17:28 +08:00
2dust
1ad840851a Adjust settings options 2025-08-08 19:44:03 +08:00
Skh-web6982
ffc74f479c Update gradle to 9.0.0 (#4820) 2025-08-08 18:45:08 +08:00
Skh-web6982
81c0087ac1 Update mmkvstatic v1.3.14 (#4819)
v1.3.14 / 2025-05-13
This is a Long Term Support (LTS) release.
This is a hot-fix release for Android/iOS/macOS users. So it is only available on Maven Central and CocoaPods.

Android
Support 16K pagesize.
Fix a potential log callback OOM crash.
Upgrade to NDK r28.1 to have full support of 16K pagesize.
iOS
Retain the callback handler to prevent a potential short-lived callback handler from crashing.
2025-08-08 17:11:24 +08:00
Skh-web6982
97327a0101 Update kotlin version to v2.2.0 (#4818)
* Update kotlin version to 2.2.0

* Update README.md : Update kotlin version to 2.2.0
2025-08-08 17:04:11 +08:00
fuilloi
6318dd554b add hevtun option (#4817) 2025-08-08 17:03:27 +08:00
2dust
785aa7eb8a up 1.10.15 2025-08-07 09:38:42 +08:00
fuilloi
872e926132 Update V2rayConfigManager.kt (#4813) 2025-08-07 09:37:06 +08:00
2dust
b278180eb5 up 1.10.14 2025-08-06 19:27:28 +08:00
2dust
ce64ffbf3b Update hev-socks5-tunnel 2025-08-06 17:46:04 +08:00
2dust
5ad8605a41 Update AndroidLibXrayLite 2025-08-06 17:46:02 +08:00
fuilloi
95f7e99752 fix hev-socks5-tunnel CustomLocalDns route (#4806)
* fix hev-socks5-tunnel dns route

* update
2025-08-06 16:54:04 +08:00
Peyman
f703a41778 Update Persian translation (#4804) 2025-08-05 19:30:19 +08:00
fuilloi
2251c59c0b fix type (#4805) 2025-08-05 18:18:00 +08:00
2dust
edd0ce96b1 remove libhev-socks5-tunnel.so 2025-08-05 18:03:18 +08:00
fuilloi
562283b3cc ndkbuild hev-socks5-tunnel (#4800) 2025-08-05 18:00:13 +08:00
solokot
bb04953845 Update Russian translation (#4797) 2025-08-05 17:13:10 +08:00
Hossein Abaspanah
208a93a917 Update Luri Bakhtiari translation (#4796) 2025-08-05 17:13:00 +08:00
2dust
92f33ee3f8 up 1.10.13 2025-08-04 20:21:17 +08:00
2dust
084346b348 Update AndroidLibXrayLite 2025-08-04 19:46:45 +08:00
2dust
15de18b736 Add Enable New TUN Feature option
When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks
2025-08-04 18:32:16 +08:00
Peyman
b60423d1c0 Update Persian translate (#4791) 2025-08-04 10:00:28 +08:00
2dust
86e38c6963 up 1.10.12 2025-08-03 17:37:32 +08:00
2dust
9ba4d7e691 Add Mldsa65Verify 2025-08-03 14:13:06 +08:00
2dust
16e72787c9 Optimize V2RayVpnService 2025-08-02 17:39:12 +08:00
2dust
3ffac8b29f Standardized file naming 2025-08-02 17:06:06 +08:00
2dust
10df1b44ea Optimize V2RayVpnService add Tun2SocksManager 2025-08-02 16:20:41 +08:00
2dust
fa12878258 Update libs.versions.toml 2025-08-02 16:08:28 +08:00
2dust
0f1ea1e119 Update hysteria 2025-08-02 16:08:15 +08:00
2dust
1959608f24 Update AndroidLibXrayLite 2025-08-02 16:08:04 +08:00
2dust
0e041a6e9a up 1.10.11 2025-07-26 20:21:01 +08:00
2dust
c78ef380cc up 1.10.10 2025-07-24 19:34:31 +08:00
2dust
57362a4bde Update AndroidLibXrayLite 2025-07-24 19:28:19 +08:00
DHR60
7f24ad534f Fix Intelligent Selection not working (#4767) 2025-07-24 19:22:15 +08:00
2dust
680832614b up 1.10.9 2025-07-10 20:20:53 +08:00
2dust
4357abbff4 Bug fix
https://github.com/2dust/v2rayNG/issues/4723
2025-07-10 20:11:34 +08:00
solokot
905be66c3f Update Russian translation (#4725) 2025-07-09 19:44:45 +08:00
Hossein Abaspanah
318a7b54a5 Update Luri Bakhtiari translation (#4724) 2025-07-09 19:44:34 +08:00
DHR60
5db2df77a0 feat. Intelligent Selection (#4716)
rename

Adds intelligent selection method setting

Adds KDoc
2025-07-07 20:17:09 +08:00
DHR60
d039cb9edf Fix export count (#4713) 2025-07-06 11:16:32 +08:00
solokot
9a1654bae9 Update Russian translation (#4701) 2025-07-01 15:23:05 +08:00
2dust
3bf911da9c up 1.10.8 2025-06-29 10:27:57 +08:00
2dust
3f778a1ea2 Optimize the source of tls sni 2025-06-28 10:02:25 +08:00
Hossein Abaspanah
8e03de8055 Update strings.xml (#4698)
Add "title_core_settings" string
2025-06-28 08:45:00 +08:00
Hossein Abaspanah
1f42d7fc07 Update strings.xml (#4696) 2025-06-27 20:39:05 +08:00
Hossein Abaspanah
0700e834f1 Update Luri Bakhtiari translation (#4695) 2025-06-27 20:38:31 +08:00
2dust
777190e861 Added setting option for Outbound domain pre-resolve method
https://github.com/2dust/v2rayNG/issues/4679
2025-06-27 17:48:31 +08:00
2dust
33572477fc Adjustment setting items 2025-06-27 16:39:06 +08:00
2dust
2fb6e62e13 Added setting option for VPN interface address
https://github.com/2dust/v2rayNG/issues/4641
2025-06-27 16:09:03 +08:00
2dust
94cc72d2b9 up 1.10.7 2025-06-19 14:40:47 +08:00
2dust
f68c353715 Update AndroidLibXrayLite 2025-06-19 14:40:11 +08:00
2dust
e077c18108 Improved update checking and prompts in case of abnormality 2025-06-19 14:40:07 +08:00
Ural Khamitov
1a5e105212 Fix blinking QSTile when QS panel is opening (#4676) 2025-06-18 16:17:28 +08:00
DHR60
e0881caab4 Fix missing sockopt.domainStrategy (#4673)
* Fix missing sockopt.domainStrategy

* Fix
2025-06-17 13:43:03 +08:00
DHR60
7219425258 Cloudflare DNS Hosts (#4661) 2025-06-15 09:46:30 +08:00
2dust
51eabe5440 up 1.10.6 2025-06-14 14:27:09 +08:00
2dust
6f0b3ce990 Update AndroidLibXrayLite 2025-06-14 14:26:37 +08:00
2dust
69e27ed3bb Fix log for plugin 2025-06-14 14:26:33 +08:00
patterniha
fff6ab30e6 Xray-core default FakeIPv6 Pool should not bypass and should route (#4649)
* Update V2RayVpnService.kt

* Update V2RayVpnService.kt

* Update AppConfig.kt
2025-06-14 13:59:59 +08:00
2dust
fdb67a86f4 up 1.10.5 2025-06-08 09:26:36 +08:00
2dust
ea088376ac Update AndroidLibXrayLite 2025-06-08 09:25:46 +08:00
2dust
52332d960e Update libs.versions.toml 2025-06-07 11:20:41 +08:00
2dust
3ead542e2b VPN bypass LAN By default 2025-06-07 11:20:37 +08:00
2dust
9d1f98ff34 Fix non-English domain
https://github.com/2dust/v2rayNG/issues/4626
f305e26a39
2025-05-31 14:03:52 +08:00
2dust
f305e26a39 Fix the parsing problem of non-English domain
https://github.com/2dust/v2rayNG/issues/4626
2025-05-31 11:12:57 +08:00
2dust
aa47fba20d up 1.10.4 2025-05-25 11:06:15 +08:00
Hossein Abaspanah
69c5bbfd3d Improved Luri Bakhtiari Translation (#4600) 2025-05-25 10:12:52 +08:00
Pk-web6936
90ed02804c Update Persian translate (#4607) 2025-05-25 10:12:45 +08:00
Hossein Abaspanah
822c1de79c Update Luri Bakhtiari translation (#4610) 2025-05-25 10:12:39 +08:00
solokot
d910b93525 Update Russian translation (#4611) 2025-05-25 10:12:29 +08:00
Pk-web6936
7e6b1c247b Update kotlin version to 2.1.21 (#4583)
* Update kotlin version to 2.1.21

* Update kotlin version to 2.1.21
2025-05-23 16:58:03 +08:00
2dust
f3f2b7fab5 Added delete function to subscription group list, secondary confirmation with settings 2025-05-23 16:17:38 +08:00
2dust
e6f260da76 Added the check update entry to the main interface drawer menu
https://github.com/2dust/v2rayNG/issues/4599
2025-05-23 14:34:55 +08:00
2dust
55bc2bf934 up 1.10.3 2025-05-17 12:01:34 +08:00
2dust
f22454da5d Update AndroidLibXrayLite 2025-05-17 11:48:15 +08:00
2dust
4a87549fa7 Update README.md 2025-05-15 10:58:52 +08:00
2dust
d447adc97f Fix
https://github.com/2dust/v2rayN/discussions/7268
2025-05-11 18:07:26 +08:00
2dust
3773962b64 up 1.10.2 2025-05-07 10:47:50 +08:00
2dust
be0a2506ce Update AndroidLibXrayLite 2025-05-07 10:44:19 +08:00
2dust
7f9cb8dfdd Check upgrade function is visible 2025-05-07 10:14:14 +08:00
2dust
71a5b6e480 Update AndroidLibXrayLite 2025-05-04 17:49:02 +08:00
76 changed files with 1912 additions and 572 deletions

View File

@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4.2.2 uses: actions/checkout@v5
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: '0' fetch-depth: '0'
@@ -31,19 +31,19 @@ jobs:
- name: Install NDK - name: Install NDK
run: | run: |
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \ echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
--channel=3 \ --channel=0 \
--install "ndk;29.0.13113456" --install "ndk;28.2.13676358"
echo "NDK_HOME=$ANDROID_HOME/ndk/29.0.13113456" >> $GITHUB_ENV echo "NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
sed -i '10i\ sed -i '10i\
\ \
ndkVersion = "29.0.13113456"' ${{ github.workspace }}/V2rayNG/app/build.gradle.kts ndkVersion = "28.2.13676358"' ${{ github.workspace }}/V2rayNG/app/build.gradle.kts
- name: Restore cached libtun2socks - name: Restore cached libtun2socks
id: cache-libtun2socks-restore id: cache-libtun2socks-restore
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
with: with:
path: ${{ github.workspace }}/libs path: ${{ github.workspace }}/libs
key: libtun2socks-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }} key: libtun2socks-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }}
- name: Build libtun2socks - name: Build libtun2socks
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true' if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
@@ -55,7 +55,7 @@ jobs:
uses: actions/cache/save@v4 uses: actions/cache/save@v4
with: with:
path: ${{ github.workspace }}/libs path: ${{ github.workspace }}/libs
key: libtun2socks-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }} key: libtun2socks-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('.git/modules/badvpn/HEAD') }}-${{ hashFiles('.git/modules/libancillary/HEAD') }}
- name: Copy libtun2socks - name: Copy libtun2socks
run: | run: |

4
.gitmodules vendored
View File

@@ -10,3 +10,7 @@
[submodule "libancillary"] [submodule "libancillary"]
path = libancillary path = libancillary
url = https://github.com/shadowsocks/libancillary url = https://github.com/shadowsocks/libancillary
[submodule "hev-socks5-tunnel"]
path = hev-socks5-tunnel
url = https://github.com/heiher/hev-socks5-tunnel
branch = master

View File

@@ -3,16 +3,12 @@
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core) A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop) [![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-2.1.20-blue.svg)](https://kotlinlang.org) [![Kotlin Version](https://img.shields.io/badge/Kotlin-2.2.0-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn) [![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
</a>
### Telegram Channel ### Telegram Channel
[github_2dust](https://t.me/github_2dust) [github_2dust](https://t.me/github_2dust)

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang" applicationId = "com.v2ray.ang"
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 651 versionCode = 669
versionName = "1.10.1" versionName = "1.10.19"
multiDexEnabled = true multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';') val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')

View File

@@ -144,6 +144,9 @@
<data android:host="install-sub" /> <data android:host="install-sub" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.CheckUpdateActivity"
android:exported="false" />
<activity <activity
android:name=".ui.AboutActivity" android:name=".ui.AboutActivity"
android:exported="false" /> android:exported="false" />

View File

@@ -26,6 +26,8 @@ object AppConfig {
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port" const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_VPN_DNS = "pref_vpn_dns" const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan" const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
const val PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX = "pref_vpn_interface_address_config_index"
const val PREF_VPN_MTU = "pref_vpn_mtu"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy" const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_RULESET = "pref_routing_ruleset" const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_MUX_ENABLED = "pref_mux_enabled"
@@ -55,10 +57,15 @@ object AppConfig {
const val PREF_DNS_HOSTS = "pref_dns_hosts" const val PREF_DNS_HOSTS = "pref_dns_hosts"
const val PREF_DELAY_TEST_URL = "pref_delay_test_url" const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel" const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method"
const val PREF_INTELLIGENT_SELECTION_METHOD = "pref_intelligent_selection_method"
const val PREF_MODE = "pref_mode" const val PREF_MODE = "pref_mode"
const val PREF_IS_BOOTED = "pref_is_booted" const val PREF_IS_BOOTED = "pref_is_booted"
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release" const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
const val PREF_GEO_FILES_SOURCES = "pref_geo_files_sources" const val PREF_GEO_FILES_SOURCES = "pref_geo_files_sources"
const val PREF_USE_HEV_TUNNEL = "pref_use_hev_tunnel"
const val PREF_HEV_TUNNEL_LOGLEVEL = "pref_hev_tunnel_loglevel"
const val PREF_HEV_TUNNEL_RW_TIMEOUT = "pref_hev_tunnel_rw_timeout"
/** Cache keys. */ /** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id" const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -84,6 +91,8 @@ object AppConfig {
const val TAG_DIRECT = "direct" const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block" const val TAG_BLOCKED = "block"
const val TAG_FRAGMENT = "fragment" const val TAG_FRAGMENT = "fragment"
const val TAG_DNS = "dns-module"
const val TAG_DOMESTIC_DNS = "domestic-dns"
/** Network-related constants. */ /** Network-related constants. */
const val UPLINK = "uplink" const val UPLINK = "uplink"
@@ -103,7 +112,7 @@ object AppConfig {
const val TG_CHANNEL_URL = "https://t.me/github_2dust" const val TG_CHANNEL_URL = "https://t.me/github_2dust"
const val DELAY_TEST_URL = "https://www.gstatic.com/generate_204" const val DELAY_TEST_URL = "https://www.gstatic.com/generate_204"
const val DELAY_TEST_URL2 = "https://www.google.com/generate_204" const val DELAY_TEST_URL2 = "https://www.google.com/generate_204"
const val IP_API_Url = "https://api.ip.sb/geoip" const val IP_API_URL = "https://speed.cloudflare.com/meta"
/** DNS server addresses. */ /** DNS server addresses. */
const val DNS_PROXY = "1.1.1.1" const val DNS_PROXY = "1.1.1.1"
@@ -160,6 +169,10 @@ object AppConfig {
/** Give a good name to this, IDK*/ /** Give a good name to this, IDK*/
const val VPN = "VPN" const val VPN = "VPN"
const val VPN_MTU = 1500
/** hev-sock5-tunnel read-write-timeout value */
const val HEVTUN_RW_TIMEOUT = "300000"
// Google API rule constants // Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn" const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"
@@ -168,7 +181,9 @@ object AppConfig {
// Android Private DNS constants // Android Private DNS constants
const val DNS_DNSPOD_DOMAIN = "dot.pub" const val DNS_DNSPOD_DOMAIN = "dot.pub"
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com" const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
const val DNS_CLOUDFLARE_DOMAIN = "one.one.one.one" const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
const val DNS_GOOGLE_DOMAIN = "dns.google" const val DNS_GOOGLE_DOMAIN = "dns.google"
const val DNS_QUAD9_DOMAIN = "dns.quad9.net" const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net" const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
@@ -182,14 +197,16 @@ object AppConfig {
const val HEADER_TYPE_HTTP = "http" const val HEADER_TYPE_HTTP = "http"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1") val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001") val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5")
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53") val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844") val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9") val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff") val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
//minimum list https://serverfault.com/a/304791 //minimum list https://serverfault.com/a/304791
val BYPASS_PRIVATE_IP_LIST = arrayListOf( val ROUTED_IP_LIST = arrayListOf(
"0.0.0.0/5", "0.0.0.0/5",
"8.0.0.0/7", "8.0.0.0/7",
"11.0.0.0/8", "11.0.0.0/8",

View File

@@ -2,10 +2,11 @@ package com.v2ray.ang.dto
data class IPAPIInfo( data class IPAPIInfo(
var ip: String? = null, var ip: String? = null,
var city: String? = null, var clientIp: String? = null,
var region: String? = null, var ip_addr: String? = null,
var region_code: String? = null, var query: String? = null,
var country: String? = null, var country: String? = null,
var country_name: String? = null, var country_name: String? = null,
var country_code: String? = null var country_code: String? = null,
var countryCode: String? = null
) )

View File

@@ -44,6 +44,7 @@ data class ProfileItem(
var publicKey: String? = null, var publicKey: String? = null,
var shortId: String? = null, var shortId: String? = null,
var spiderX: String? = null, var spiderX: String? = null,
var mldsa65Verify: String? = null,
var secretKey: String? = null, var secretKey: String? = null,
var preSharedKey: String? = null, var preSharedKey: String? = null,

View File

@@ -11,6 +11,7 @@ data class SubscriptionItem(
var prevProfile: String? = null, var prevProfile: String? = null,
var nextProfile: String? = null, var nextProfile: String? = null,
var filter: String? = null, var filter: String? = null,
var intelligentSelectionFilter: String? = null,
var allowInsecureUrl: Boolean = false, var allowInsecureUrl: Boolean = false,
) )

View File

@@ -245,7 +245,14 @@ data class V2rayConfig(
var tproxy: String? = null, var tproxy: String? = null,
var mark: Int? = null, var mark: Int? = null,
var dialerProxy: String? = null, var dialerProxy: String? = null,
var domainStrategy: String? = null var domainStrategy: String? = null,
var happyEyeballs: happyEyeballsBean? = null,
)
data class happyEyeballsBean(
var prioritizeIPv6: Boolean? = null,
var maxConcurrentTry: Int? = 4,
var tryDelayMs: Int? = 250, // ms
var interleave: Int? = null,
) )
data class TlsSettingsBean( data class TlsSettingsBean(
@@ -264,7 +271,8 @@ data class V2rayConfig(
val show: Boolean = false, val show: Boolean = false,
var publicKey: String? = null, var publicKey: String? = null,
var shortId: String? = null, var shortId: String? = null,
var spiderX: String? = null var spiderX: String? = null,
var mldsa65Verify: String? = null
) )
data class QuicSettingBean( data class QuicSettingBean(
@@ -489,6 +497,7 @@ data class V2rayConfig(
var expectIPs: List<String>? = null, var expectIPs: List<String>? = null,
val clientIp: String? = null, val clientIp: String? = null,
val skipFallback: Boolean? = null, val skipFallback: Boolean? = null,
val tag: String? = null,
) )
} }
@@ -496,14 +505,14 @@ data class V2rayConfig(
var domainStrategy: String, var domainStrategy: String,
var domainMatcher: String? = null, var domainMatcher: String? = null,
var rules: ArrayList<RulesBean>, var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null var balancers: List<BalancerBean>? = null
) { ) {
data class RulesBean( data class RulesBean(
var type: String = "field", var type: String = "field",
var ip: ArrayList<String>? = null, var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null, var domain: ArrayList<String>? = null,
var outboundTag: String = "", var outboundTag: String? = null,
var balancerTag: String? = null, var balancerTag: String? = null,
var port: String? = null, var port: String? = null,
val sourcePort: String? = null, val sourcePort: String? = null,
@@ -515,6 +524,32 @@ data class V2rayConfig(
val attrs: String? = null, val attrs: String? = null,
val domainMatcher: String? = null val domainMatcher: String? = null
) )
data class BalancerBean(
val tag: String,
val selector: List<String>,
val fallbackTag: String? = null,
val strategy: StrategyObject? = null
)
data class StrategyObject(
val type: String = "random", // "random" | "roundRobin" | "leastPing" | "leastLoad"
val settings: StrategySettingsObject? = null
)
data class StrategySettingsObject(
val expected: Int? = null,
val maxRTT: String? = null,
val tolerance: Double? = null,
val baselines: List<String>? = null,
val costs: List<CostObject>? = null
)
data class CostObject(
val regexp: Boolean = false,
val match: String,
val value: Double
)
} }
data class PolicyBean( data class PolicyBean(
@@ -532,6 +567,26 @@ data class V2rayConfig(
) )
} }
data class ObservatoryObject(
val subjectSelector: List<String>,
val probeUrl: String,
val probeInterval: String,
val enableConcurrency: Boolean = false
)
data class BurstObservatoryObject(
val subjectSelector: List<String>,
val pingConfig: PingConfigObject
) {
data class PingConfigObject(
val destination: String,
val connectivity: String? = null,
val interval: String,
val sampling: Int,
val timeout: String? = null
)
}
data class FakednsBean( data class FakednsBean(
var ipPool: String = "198.18.0.0/15", var ipPool: String = "198.18.0.0/15",
var poolSize: Int = 10000 var poolSize: Int = 10000

View File

@@ -0,0 +1,39 @@
package com.v2ray.ang.dto
/**
* VPN interface address configuration enum class
* Defines predefined IPv4 and IPv6 address pairs for VPN TUN interface configuration.
* Each option provides client and router addresses to establish point-to-point VPN tunnels.
*/
enum class VpnInterfaceAddressConfig(
val displayName: String,
val ipv4Client: String,
val ipv4Router: String,
val ipv6Client: String,
val ipv6Router: String
) {
OPTION_1("10.10.14.x", "10.10.14.1", "10.10.14.2", "fc00::10:10:14:1", "fc00::10:10:14:2"),
OPTION_2("10.1.0.x", "10.1.0.1", "10.1.0.2", "fc00::10:1:0:1", "fc00::10:1:0:2"),
OPTION_3("10.0.0.x", "10.0.0.1", "10.0.0.2", "fc00::10:0:0:1", "fc00::10:0:0:2"),
OPTION_4("172.31.0.x", "172.31.0.1", "172.31.0.2", "fc00::172:31:0:1", "fc00::172:31:0:2"),
OPTION_5("172.20.0.x", "172.20.0.1", "172.20.0.2", "fc00::172:20:0:1", "fc00::172:20:0:2"),
OPTION_6("172.16.0.x", "172.16.0.1", "172.16.0.2", "fc00::172:16:0:1", "fc00::172:16:0:2"),
OPTION_7("192.168.100.x", "192.168.100.1", "192.168.100.2", "fc00::192:168:100:1", "fc00::192:168:100:2");
companion object {
/**
* Retrieves the VPN interface address configuration based on the specified index.
*
* @param index The configuration index (0-based) corresponding to user selection
* @return The VpnInterfaceAddressConfig instance at the specified index,
* or OPTION_1 (default) if the index is out of bounds
*/
fun getConfigByIndex(index: Int): VpnInterfaceAddressConfig {
return if (index in values().indices) {
values()[index]
} else {
OPTION_1 // Default to the first configuration
}
}
}
}

View File

@@ -4,6 +4,8 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.NetworkType import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.isNotNullEmpty import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
@@ -26,7 +28,7 @@ open class FmtBase {
val url = String.format( val url = String.format(
"%s@%s:%s", "%s@%s:%s",
Utils.urlEncode(userInfo ?: ""), Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(config.server), Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
config.serverPort config.serverPort
) )
@@ -81,6 +83,7 @@ open class FmtBase {
config.publicKey = queryParam["pbk"] config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"] config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"] config.spiderX = queryParam["spx"]
config.mldsa65Verify = queryParam["pqv"]
config.flow = queryParam["flow"] config.flow = queryParam["flow"]
} }
@@ -99,6 +102,7 @@ open class FmtBase {
config.publicKey.let { if (it.isNotNullEmpty()) dicQuery["pbk"] = it.orEmpty() } config.publicKey.let { if (it.isNotNullEmpty()) dicQuery["pbk"] = it.orEmpty() }
config.shortId.let { if (it.isNotNullEmpty()) dicQuery["sid"] = it.orEmpty() } config.shortId.let { if (it.isNotNullEmpty()) dicQuery["sid"] = it.orEmpty() }
config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() } config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() }
config.mldsa65Verify.let { if (it.isNotNullEmpty()) dicQuery["pqv"] = it.orEmpty() }
config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() } config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
val networkType = NetworkType.fromString(config.network) val networkType = NetworkType.fromString(config.network)
@@ -149,6 +153,20 @@ open class FmtBase {
return dicQuery return dicQuery
} }
fun getServerAddress(profileItem: ProfileItem): String {
if (Utils.isPureIpAddress(profileItem.server.orEmpty())) {
return profileItem.server.orEmpty()
}
val domain = HttpUtil.toIdnDomain(profileItem.server.orEmpty())
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") != "2") {
return domain
}
//Resolve and replace domain
val resolvedIps = HttpUtil.resolveHostToIP(domain, MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
if (resolvedIps.isNullOrEmpty()) {
return domain
}
return resolvedIps.first()
}
} }

View File

@@ -17,7 +17,7 @@ object HttpFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP)
outboundBean?.settings?.servers?.first()?.let { server -> outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty() server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt() server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) { if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()

View File

@@ -135,7 +135,7 @@ object ShadowsocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.first()?.let { server -> outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty() server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt() server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password server.password = profileItem.password
server.method = profileItem.method server.method = profileItem.method

View File

@@ -64,7 +64,7 @@ object SocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS)
outboundBean?.settings?.servers?.first()?.let { server -> outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty() server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt() server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) { if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean() val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()

View File

@@ -64,7 +64,7 @@ object TrojanFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN)
outboundBean?.settings?.servers?.first()?.let { server -> outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty() server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt() server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password server.password = profileItem.password
server.flow = profileItem.flow server.flow = profileItem.flow

View File

@@ -60,7 +60,7 @@ object VlessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS)
outboundBean?.settings?.vnext?.first()?.let { vnext -> outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty() vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt() vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty() vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].encryption = profileItem.method vnext.users[0].encryption = profileItem.method

View File

@@ -172,7 +172,7 @@ object VmessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS) val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS)
outboundBean?.settings?.vnext?.first()?.let { vnext -> outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty() vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt() vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty() vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].security = profileItem.method vnext.users[0].security = profileItem.method

View File

@@ -71,7 +71,7 @@ object AngConfigManager {
if (sb.count() > 0) { if (sb.count() > 0) {
Utils.setClipboard(context, sb.toString()) Utils.setClipboard(context, sb.toString())
} }
return sb.lines().count() return sb.lines().count() - 1
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to share non-custom configs to clipboard", e) Log.e(AppConfig.TAG, "Failed to share non-custom configs to clipboard", e)
return -1 return -1
@@ -415,7 +415,7 @@ object AngConfigManager {
if (!it.second.enabled) { if (!it.second.enabled) {
return 0 return 0
} }
val url = HttpUtil.idnToASCII(it.second.url) val url = HttpUtil.toIdnUrl(it.second.url)
if (!Utils.isValidUrl(url)) { if (!Utils.isValidUrl(url)) {
return 0 return 0
} }
@@ -490,4 +490,31 @@ object AngConfigManager {
MmkvManager.encodeSubscription("", subItem) MmkvManager.encodeSubscription("", subItem)
return 1 return 1
} }
/**
* Creates an intelligent selection configuration based on multiple server configurations.
*
* @param context The application context used for configuration generation.
* @param guidList The list of server GUIDs to be included in the intelligent selection.
* Each GUID represents a server configuration that will be combined.
* @param subid The subscription ID to associate with the generated configuration.
* This helps organize the configuration under a specific subscription.
* @return The GUID key of the newly created intelligent selection configuration,
* or null if the operation fails (e.g., empty guidList or configuration parsing error).
*/
fun createIntelligentSelection(
context: Context,
guidList: List<String>,
subid: String
): String? {
if (guidList.isEmpty()) {
return null
}
val result = V2rayConfigManager.genV2rayConfig(context, guidList) ?: return null
val config = CustomFmt.parse(JsonUtil.toJson(result)) ?: return null
config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(result) ?: "")
return key
}
} }

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service package com.v2ray.ang.handler
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
@@ -12,12 +12,10 @@ import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.MainActivity
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -27,7 +25,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.min import kotlin.math.min
object NotificationService { object NotificationManager {
private const val NOTIFICATION_ID = 1 private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0 private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1 private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
@@ -50,7 +48,7 @@ object NotificationService {
lastQueryTime = System.currentTimeMillis() lastQueryTime = System.currentTimeMillis()
var lastZeroSpeed = false var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags() val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT) outboundTags?.remove(AppConfig.TAG_DIRECT)
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch { speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) { while (isActive) {
@@ -66,15 +64,15 @@ object NotificationService {
proxyTotal += up + down proxyTotal += up + down
} }
} }
val directUplink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.UPLINK) val directUplink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.DOWNLINK) val directDownlink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK)
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
if (!zeroSpeed || !lastZeroSpeed) { if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) { if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0) appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
} }
appendSpeedString( appendSpeedString(
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds, text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds directDownlink / sinceLastQueryInSeconds
) )
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink) updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
@@ -102,12 +100,12 @@ object NotificationService {
val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags) val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE) val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = ANG_PACKAGE stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP) stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags) val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE) val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
restartV2RayIntent.`package` = ANG_PACKAGE restartV2RayIntent.`package` = AppConfig.ANG_PACKAGE
restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART) restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags) val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.util package com.v2ray.ang.handler
import android.content.Context import android.content.Context
import android.os.SystemClock import android.os.SystemClock
@@ -7,11 +7,12 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.fmt.Hysteria2Fmt import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.service.ProcessService import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import java.io.File import java.io.File
object PluginUtil { object PluginServiceManager {
private const val HYSTERIA2 = "libhysteria2.so" private const val HYSTERIA2 = "libhysteria2.so"
private val procService: ProcessService by lazy { private val procService: ProcessService by lazy {
@@ -28,13 +29,17 @@ object PluginUtil {
fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) { fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) {
Log.i(AppConfig.TAG, "Starting plugin execution") Log.i(AppConfig.TAG, "Starting plugin execution")
if (config == null || socksPort == null) { if (config == null) {
Log.w(AppConfig.TAG, "Cannot run plugin: config is null") Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
return return
} }
try { try {
if (config.configType == EConfigType.HYSTERIA2) { if (config.configType == EConfigType.HYSTERIA2) {
if (socksPort == null) {
Log.w(AppConfig.TAG, "Cannot run plugin: socksPort is null")
return
}
Log.i(AppConfig.TAG, "Running Hysteria2 plugin") Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
val configFile = genConfigHy2(context, config, socksPort) ?: return val configFile = genConfigHy2(context, config, socksPort) ?: return
val cmd = genCmdHy2(context, configFile) val cmd = genCmdHy2(context, configFile)

View File

@@ -16,6 +16,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RoutingType import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VpnInterfaceAddressConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
import com.v2ray.ang.handler.MmkvManager.decodeServerList import com.v2ray.ang.handler.MmkvManager.decodeServerList
import com.v2ray.ang.util.JsonUtil import com.v2ray.ang.util.JsonUtil
@@ -159,7 +160,7 @@ object SettingsManager {
* @return True if bypassing LAN, false otherwise. * @return True if bypassing LAN, false otherwise.
*/ */
fun routingRulesetsBypassLan(): Boolean { fun routingRulesetsBypassLan(): Boolean {
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "0" val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "1"
if (vpnBypassLan == "1") { if (vpnBypassLan == "1") {
return true return true
} else if (vpnBypassLan == "2") { } else if (vpnBypassLan == "2") {
@@ -356,4 +357,24 @@ object SettingsManager {
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) "2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} }
} }
/**
* Retrieves the currently selected VPN interface address configuration.
* This method reads the user's preference for VPN interface addressing and returns
* the corresponding configuration containing IPv4 and IPv6 addresses.
*
* @return The selected VpnInterfaceAddressConfig instance, or the default configuration
* if no valid selection is found or if the stored index is invalid.
*/
fun getCurrentVpnInterfaceAddressConfig(): VpnInterfaceAddressConfig {
val selectedIndex = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX, "0")?.toInt()
return VpnInterfaceAddressConfig.getConfigByIndex(selectedIndex ?: 0)
}
/**
* Get the VPN MTU from settings, defaulting to AppConfig.VPN_MTU.
*/
fun getVpnMtu(): Int {
return Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU), AppConfig.VPN_MTU)
}
} }

View File

@@ -168,10 +168,13 @@ object SpeedtestManager {
fun getRemoteIPInfo(): String? { fun getRemoteIPInfo(): String? {
val httpPort = SettingsManager.getHttpPort() val httpPort = SettingsManager.getHttpPort()
var content = HttpUtil.getUrlContent(AppConfig.IP_API_Url, 5000, httpPort) ?: return null var content = HttpUtil.getUrlContent(AppConfig.IP_API_URL, 5000, httpPort) ?: return null
var ipInfo = JsonUtil.fromJson(content, IPAPIInfo::class.java) ?: return null var ipInfo = JsonUtil.fromJson(content, IPAPIInfo::class.java) ?: return null
return "(${ipInfo.country_code}) ${ipInfo.ip}" var ip = ipInfo.ip ?: ipInfo.clientIp ?: ipInfo.ip_addr ?: ipInfo.query
var country = ipInfo.country_code ?: ipInfo.country ?: ipInfo.countryCode
return "(${country ?: "unknown"}) $ip"
} }
/** /**

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service package com.v2ray.ang.handler
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationChannel import android.app.NotificationChannel
@@ -11,11 +11,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.handler.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.handler.MmkvManager
object SubscriptionUpdater { object SubscriptionUpdater {
@@ -24,7 +20,7 @@ object SubscriptionUpdater {
private val notificationManager = NotificationManagerCompat.from(applicationContext) private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification = private val notification =
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL) NotificationCompat.Builder(applicationContext, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
.setWhen(0) .setWhen(0)
.setTicker("Update") .setTicker("Update")
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription)) .setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
@@ -46,18 +42,18 @@ object SubscriptionUpdater {
val subItem = sub.second val subItem = sub.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL) notification.setChannelId(AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
val channel = val channel =
NotificationChannel( NotificationChannel(
SUBSCRIPTION_UPDATE_CHANNEL, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL,
SUBSCRIPTION_UPDATE_CHANNEL_NAME, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_MIN NotificationManager.IMPORTANCE_MIN
) )
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
notificationManager.notify(3, notification.build()) notificationManager.notify(3, notification.build())
Log.i(AppConfig.TAG, "subscription automatic update: ---${subItem.remarks}") Log.i(AppConfig.TAG, "subscription automatic update: ---${subItem.remarks}")
updateConfigViaSub(Pair(sub.first, subItem)) AngConfigManager.updateConfigViaSub(Pair(sub.first, subItem))
notification.setContentText("Updating ${subItem.remarks}") notification.setContentText("Updating ${subItem.remarks}")
} }
notificationManager.cancel(3) notificationManager.cancel(3)

View File

@@ -17,7 +17,6 @@ import java.io.FileOutputStream
object UpdateCheckerManager { object UpdateCheckerManager {
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) { suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
try {
val url = if (includePreRelease) { val url = if (includePreRelease) {
AppConfig.APP_API_URL AppConfig.APP_API_URL
} else { } else {
@@ -53,10 +52,6 @@ object UpdateCheckerManager {
} else { } else {
CheckUpdateResult(hasUpdate = false) CheckUpdateResult(hasUpdate = false)
} }
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
return@withContext CheckUpdateResult(hasUpdate = false, error = e.message)
}
} }
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) { suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service package com.v2ray.ang.handler
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@@ -13,12 +13,11 @@ import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.service.ServiceControl
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.service.V2RayProxyOnlyService
import com.v2ray.ang.handler.SpeedtestManager import com.v2ray.ang.service.V2RayVpnService
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil import com.v2ray.ang.handler.PluginServiceManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import go.Seq import go.Seq
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -102,7 +101,7 @@ object V2RayServiceManager {
val config = MmkvManager.decodeServerConfig(guid) ?: return val config = MmkvManager.decodeServerConfig(guid) ?: return
if (config.configType != EConfigType.CUSTOM if (config.configType != EConfigType.CUSTOM
&& !Utils.isValidUrl(config.server) && !Utils.isValidUrl(config.server)
&& !Utils.isIpAddress(config.server) && !Utils.isPureIpAddress(config.server.orEmpty())
) return ) return
// val result = V2rayConfigUtil.getV2rayConfig(context, guid) // val result = V2rayConfigUtil.getV2rayConfig(context, guid)
// if (!result.status) return // if (!result.status) return
@@ -163,16 +162,16 @@ object V2RayServiceManager {
if (coreController.isRunning == false) { if (coreController.isRunning == false) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "") MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
NotificationService.cancelNotification() NotificationManager.cancelNotification()
return false return false
} }
try { try {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "") MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
NotificationService.showNotification(currentConfig) NotificationManager.showNotification(currentConfig)
NotificationService.startSpeedNotification(currentConfig) NotificationManager.startSpeedNotification(currentConfig)
PluginUtil.runPlugin(service, config, result.socksPort) PluginServiceManager.runPlugin(service, config, result.socksPort)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to startup service", e) Log.e(AppConfig.TAG, "Failed to startup service", e)
return false return false
@@ -199,14 +198,14 @@ object V2RayServiceManager {
} }
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "") MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
NotificationService.cancelNotification() NotificationManager.cancelNotification()
try { try {
service.unregisterReceiver(mMsgReceive) service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e) Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
} }
PluginUtil.stopPlugin() PluginServiceManager.stopPlugin()
return true return true
} }
@@ -364,12 +363,12 @@ object V2RayServiceManager {
when (intent?.action) { when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> { Intent.ACTION_SCREEN_OFF -> {
Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats") Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats")
NotificationService.stopSpeedNotification(currentConfig) NotificationManager.stopSpeedNotification(currentConfig)
} }
Intent.ACTION_SCREEN_ON -> { Intent.ACTION_SCREEN_ON -> {
Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats") Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats")
NotificationService.startSpeedNotification(currentConfig) NotificationManager.startSpeedNotification(currentConfig)
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.ConfigResult import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.NetworkType import com.v2ray.ang.dto.NetworkType
@@ -15,8 +16,8 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
import com.v2ray.ang.extension.isNotNullEmpty import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.HttpFmt import com.v2ray.ang.fmt.HttpFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt import com.v2ray.ang.fmt.SocksFmt
import com.v2ray.ang.fmt.TrojanFmt import com.v2ray.ang.fmt.TrojanFmt
@@ -53,6 +54,27 @@ object V2rayConfigManager {
} }
} }
/**
* Generates a V2ray configuration from multiple server profiles.
*
* @param context The context of the caller.
* @param guidList A list of server GUIDs to be included in the generated configuration.
* Each GUID represents a unique server profile stored in the system.
* @return A V2rayConfig object containing the combined configuration of all specified servers,
* or null if the operation fails (e.g., no valid configurations found, parsing errors)
*/
fun genV2rayConfig(context: Context, guidList: List<String>): V2rayConfig? {
try {
val configList = guidList.mapNotNull { guid ->
MmkvManager.decodeServerConfig(guid)
}
return genV2rayMultipleConfig(context, configList)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to generate V2ray config", e)
return null
}
}
/** /**
* Retrieves the speedtest V2ray configuration for the given GUID. * Retrieves the speedtest V2ray configuration for the given GUID.
* *
@@ -98,7 +120,7 @@ object V2rayConfigManager {
val result = ConfigResult(false) val result = ConfigResult(false)
val address = config.server ?: return result val address = config.server ?: return result
if (!Utils.isIpAddress(address)) { if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) { if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain") Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result return result
@@ -132,7 +154,10 @@ object V2rayConfigManager {
v2rayConfig.policy = null v2rayConfig.policy = null
} }
resolveOutboundDomainsToHosts(v2rayConfig) //Resolve and add to DNS Hosts
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
resolveOutboundDomainsToHosts(v2rayConfig)
}
result.status = true result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: "" result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
@@ -140,6 +165,80 @@ object V2rayConfigManager {
return result return result
} }
private fun genV2rayMultipleConfig(context: Context, configList: List<ProfileItem>): V2rayConfig? {
val validConfigs = configList.asSequence().filter { it.server.isNotNullEmpty() }
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
.filter { it.configType != EConfigType.CUSTOM }
.filter { it.configType != EConfigType.HYSTERIA2 }
.filter { config ->
if (config.subscriptionId.isEmpty()) {
return@filter true
}
val subItem = MmkvManager.decodeSubscription(config.subscriptionId)
if (subItem?.intelligentSelectionFilter.isNullOrEmpty() || config.remarks.isEmpty()) {
return@filter true
}
Regex(pattern = subItem?.intelligentSelectionFilter!!).containsMatchIn(input = config.remarks)
}.toList()
if (validConfigs.isEmpty()) {
Log.w(AppConfig.TAG, "All configs are invalid")
return null
}
val v2rayConfig = initV2rayConfig(context) ?: return null
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
val subIds = configList.map { it.subscriptionId }.toHashSet()
val remarks = if (subIds.size == 1 && subIds.first().isNotEmpty()) {
val sub = MmkvManager.decodeSubscription(subIds.first())
(sub?.remarks ?: "") + context.getString(R.string.intelligent_selection)
} else {
context.getString(R.string.intelligent_selection)
}
v2rayConfig.remarks = remarks
getInbounds(v2rayConfig)
v2rayConfig.outbounds.removeAt(0)
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
var index = 0
for (config in validConfigs) {
index++
val outbound = convertProfile2Outbound(config) ?: continue
val ret = updateOutboundWithGlobalSettings(outbound)
if (!ret) continue
outbound.tag = "proxy-$index"
outboundsList.add(outbound)
}
outboundsList.addAll(v2rayConfig.outbounds)
v2rayConfig.outbounds = ArrayList(outboundsList)
getRouting(v2rayConfig)
getFakeDns(v2rayConfig)
getDns(v2rayConfig)
getBalance(v2rayConfig)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
getCustomLocalDns(v2rayConfig)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
v2rayConfig.policy = null
}
//Resolve and add to DNS Hosts
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
resolveOutboundDomainsToHosts(v2rayConfig)
}
return v2rayConfig
}
/** /**
* Retrieves the normal V2ray configuration for speedtest. * Retrieves the normal V2ray configuration for speedtest.
* *
@@ -152,7 +251,7 @@ object V2rayConfigManager {
val result = ConfigResult(false) val result = ConfigResult(false)
val address = config.server ?: return result val address = config.server ?: return result
if (!Utils.isIpAddress(address)) { if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) { if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain") Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result return result
@@ -371,27 +470,49 @@ object V2rayConfigManager {
) )
} }
// DNS inbound if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL) == false) {
val remoteDns = SettingsManager.getRemoteDnsServers()
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else AppConfig.DNS_PROXY,
port = 53,
network = "tcp,udp"
)
val localDnsPort = Utils.parseInt( // DNS inbound
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), val remoteDns = SettingsManager.getRemoteDnsServers()
AppConfig.PORT_LOCAL_DNS.toInt() if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else AppConfig.DNS_PROXY,
port = 53,
network = "tcp,udp"
)
val localDnsPort = Utils.parseInt(
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
)
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
port = localDnsPort,
listen = AppConfig.LOOPBACK,
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null
)
)
}
// DNS routing tag
v2rayConfig.routing.rules.add(
0, RulesBean(
inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out",
domain = null
)
) )
v2rayConfig.inbounds.add( } else {
V2rayConfig.InboundBean( //hev-socks5-tunnel dns routing
tag = "dns-in", v2rayConfig.routing.rules.add(
port = localDnsPort, 0, RulesBean(
listen = AppConfig.LOOPBACK, inboundTag = arrayListOf("socks"),
protocol = "dokodemo-door", outboundTag = "dns-out",
settings = dnsInboundSettings, port = "53",
sniffing = null type = "field"
) )
) )
} }
@@ -408,15 +529,6 @@ object V2rayConfigManager {
) )
) )
} }
// DNS routing tag
v2rayConfig.routing.rules.add(
0, RulesBean(
inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out",
domain = null
)
)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to configure custom local DNS", e) Log.e(AppConfig.TAG, "Failed to configure custom local DNS", e)
return false return false
@@ -463,22 +575,31 @@ object V2rayConfigManager {
address = domesticDns.first(), address = domesticDns.first(),
domains = directDomain, domains = directDomain,
expectIPs = if (isCnRoutingMode) geoipCn else null, expectIPs = if (isCnRoutingMode) geoipCn else null,
skipFallback = true skipFallback = true,
tag = AppConfig.TAG_DOMESTIC_DNS
) )
) )
} }
if (Utils.isPureIpAddress(domesticDns.first())) { //block dns
v2rayConfig.routing.rules.add( val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
0, RulesBean( if (blkDomain.isNotEmpty()) {
outboundTag = AppConfig.TAG_DIRECT, hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null
)
)
} }
// hardcode googleapi rule to fix play store problems
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
// hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
//User DNS hosts //User DNS hosts
try { try {
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS) val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
@@ -493,41 +614,29 @@ object V2rayConfigManager {
Log.e(AppConfig.TAG, "Failed to configure user DNS hosts", e) Log.e(AppConfig.TAG, "Failed to configure user DNS hosts", e)
} }
//block dns
val blkDomain = getUserRule2Domain(AppConfig.TAG_BLOCKED)
if (blkDomain.isNotEmpty()) {
hosts.putAll(blkDomain.map { it to AppConfig.LOOPBACK })
}
// hardcode googleapi rule to fix play store problems
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
// hardcode popular Android Private DNS rule to fix localhost DNS problem
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
hosts[AppConfig.DNS_CLOUDFLARE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ADDRESSES
hosts[AppConfig.DNS_DNSPOD_DOMAIN] = AppConfig.DNS_DNSPOD_ADDRESSES
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
// DNS dns // DNS dns
v2rayConfig.dns = V2rayConfig.DnsBean( v2rayConfig.dns = V2rayConfig.DnsBean(
servers = servers, servers = servers,
hosts = hosts hosts = hosts,
tag = AppConfig.TAG_DNS
) )
// DNS routing // DNS routing
if (Utils.isPureIpAddress(remoteDns.first())) { v2rayConfig.routing.rules.add(
v2rayConfig.routing.rules.add( 0, RulesBean(
0, RulesBean( outboundTag = AppConfig.TAG_PROXY,
outboundTag = AppConfig.TAG_PROXY, inboundTag = arrayListOf(AppConfig.TAG_DNS),
port = "53", domain = null
ip = arrayListOf(remoteDns.first()),
domain = null
)
) )
} )
v2rayConfig.routing.rules.add(
0, RulesBean(
outboundTag = AppConfig.TAG_DIRECT,
inboundTag = arrayListOf(AppConfig.TAG_DOMESTIC_DNS),
domain = null
)
)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to configure DNS", e) Log.e(AppConfig.TAG, "Failed to configure DNS", e)
return false return false
@@ -738,6 +847,78 @@ object V2rayConfigManager {
return true return true
} }
/**
* Configures load balancing settings for the V2ray configuration.
*
* @param v2rayConfig The V2ray configuration object to be modified with balancing settings
*/
private fun getBalance(v2rayConfig: V2rayConfig)
{
try {
v2rayConfig.routing.rules.forEach { rule ->
if (rule.outboundTag == "proxy") {
rule.outboundTag = null
rule.balancerTag = "proxy-round"
}
}
if (MmkvManager.decodeSettingsString(AppConfig.PREF_INTELLIGENT_SELECTION_METHOD, "0") == "0") {
val balancer = V2rayConfig.RoutingBean.BalancerBean(
tag = "proxy-round",
selector = listOf("proxy-"),
strategy = V2rayConfig.RoutingBean.StrategyObject(
type = "leastPing"
)
)
v2rayConfig.routing.balancers = listOf(balancer)
v2rayConfig.observatory = V2rayConfig.ObservatoryObject(
subjectSelector = listOf("proxy-"),
probeUrl = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DELAY_TEST_URL,
probeInterval = "3m",
enableConcurrency = true
)
} else {
val balancer = V2rayConfig.RoutingBean.BalancerBean(
tag = "proxy-round",
selector = listOf("proxy-"),
strategy = V2rayConfig.RoutingBean.StrategyObject(
type = "leastLoad"
)
)
v2rayConfig.routing.balancers = listOf(balancer)
v2rayConfig.burstObservatory = V2rayConfig.BurstObservatoryObject(
subjectSelector = listOf("proxy-"),
pingConfig = V2rayConfig.BurstObservatoryObject.PingConfigObject(
destination = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DELAY_TEST_URL,
interval = "5m",
sampling = 2,
timeout = "30s"
)
)
}
if (v2rayConfig.routing.domainStrategy == "IPIfNonMatch") {
v2rayConfig.routing.rules.add(
RulesBean(
ip = arrayListOf("0.0.0.0/0", "::/0"),
balancerTag = "proxy-round",
type = "field"
)
)
} else {
v2rayConfig.routing.rules.add(
RulesBean(
network = "tcp,udp",
balancerTag = "proxy-round",
type = "field"
)
)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to configure balance", e)
}
}
/** /**
* Updates the outbound with fragment settings for traffic optimization. * Updates the outbound with fragment settings for traffic optimization.
* *
@@ -828,12 +1009,24 @@ object V2rayConfigManager {
for (item in proxyOutboundList) { for (item in proxyOutboundList) {
val domain = item.getServerAddress() val domain = item.getServerAddress()
if (domain.isNullOrEmpty()) continue if (domain.isNullOrEmpty()) continue
if (newHosts.containsKey(domain)) continue
if (newHosts.containsKey(domain)) {
item.ensureSockopt().domainStrategy = "UseIP"
item.ensureSockopt().happyEyeballs = StreamSettingsBean.happyEyeballsBean(
prioritizeIPv6 = preferIpv6,
interleave = 2
)
continue
}
val resolvedIps = HttpUtil.resolveHostToIP(domain, preferIpv6) val resolvedIps = HttpUtil.resolveHostToIP(domain, preferIpv6)
if (resolvedIps.isNullOrEmpty()) continue if (resolvedIps.isNullOrEmpty()) continue
item.ensureSockopt().domainStrategy = if (preferIpv6) "UseIPv6v4" else "UseIPv4v6" item.ensureSockopt().domainStrategy = "UseIP"
item.ensureSockopt().happyEyeballs = StreamSettingsBean.happyEyeballsBean(
prioritizeIPv6 = preferIpv6,
interleave = 2
)
newHosts[domain] = if (resolvedIps.size == 1) { newHosts[domain] = if (resolvedIps.size == 1) {
resolvedIps[0] resolvedIps[0]
} else { } else {
@@ -1045,12 +1238,21 @@ object V2rayConfigManager {
fun populateTlsSettings(streamSettings: StreamSettingsBean, profileItem: ProfileItem, sniExt: String?) { fun populateTlsSettings(streamSettings: StreamSettingsBean, profileItem: ProfileItem, sniExt: String?) {
val streamSecurity = profileItem.security.orEmpty() val streamSecurity = profileItem.security.orEmpty()
val allowInsecure = profileItem.insecure == true val allowInsecure = profileItem.insecure == true
val sni = if (profileItem.sni.isNullOrEmpty()) sniExt else profileItem.sni val sni = if (profileItem.sni.isNullOrEmpty()) {
when {
sniExt.isNotNullEmpty() && Utils.isDomainName(sniExt) -> sniExt
profileItem.server.isNotNullEmpty() && Utils.isDomainName(profileItem.server) -> profileItem.server
else -> sniExt
}
} else {
profileItem.sni
}
val fingerprint = profileItem.fingerPrint val fingerprint = profileItem.fingerPrint
val alpns = profileItem.alpn val alpns = profileItem.alpn
val publicKey = profileItem.publicKey val publicKey = profileItem.publicKey
val shortId = profileItem.shortId val shortId = profileItem.shortId
val spiderX = profileItem.spiderX val spiderX = profileItem.spiderX
val mldsa65Verify = profileItem.mldsa65Verify
streamSettings.security = if (streamSecurity.isEmpty()) null else streamSecurity streamSettings.security = if (streamSecurity.isEmpty()) null else streamSecurity
if (streamSettings.security == null) return if (streamSettings.security == null) return
@@ -1062,6 +1264,7 @@ object V2rayConfigManager {
publicKey = if (publicKey.isNullOrEmpty()) null else publicKey, publicKey = if (publicKey.isNullOrEmpty()) null else publicKey,
shortId = if (shortId.isNullOrEmpty()) null else shortId, shortId = if (shortId.isNullOrEmpty()) null else shortId,
spiderX = if (spiderX.isNullOrEmpty()) null else spiderX, spiderX = if (spiderX.isNullOrEmpty()) null else spiderX,
mldsa65Verify = if (mldsa65Verify.isNullOrEmpty()) null else mldsa65Verify,
) )
if (streamSettings.security == AppConfig.TLS) { if (streamSettings.security == AppConfig.TLS) {
streamSettings.tlsSettings = tlsSetting streamSettings.tlsSettings = tlsSetting

View File

@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
/** /**

View File

@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
class TaskerReceiver : BroadcastReceiver() { class TaskerReceiver : BroadcastReceiver() {

View File

@@ -10,7 +10,7 @@ import android.os.Build
import android.widget.RemoteViews import android.widget.RemoteViews
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
class WidgetProvider : AppWidgetProvider() { class WidgetProvider : AppWidgetProvider() {
/** /**

View File

@@ -13,6 +13,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
@@ -25,14 +26,13 @@ class QSTileService : TileService() {
* @param state The state to set. * @param state The state to set.
*/ */
fun setState(state: Int) { fun setState(state: Int) {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
if (state == Tile.STATE_INACTIVE) { if (state == Tile.STATE_INACTIVE) {
qsTile?.state = Tile.STATE_INACTIVE qsTile?.state = Tile.STATE_INACTIVE
qsTile?.label = getString(R.string.app_name) qsTile?.label = getString(R.string.app_name)
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
} else if (state == Tile.STATE_ACTIVE) { } else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = V2RayServiceManager.getRunningServerName() qsTile?.label = V2RayServiceManager.getRunningServerName()
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
} }
qsTile?.updateTile() qsTile?.updateTile()
@@ -45,7 +45,11 @@ class QSTileService : TileService() {
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
setState(Tile.STATE_INACTIVE) if (V2RayServiceManager.isRunning()) {
setState(Tile.STATE_ACTIVE)
} else {
setState(Tile.STATE_INACTIVE)
}
mMsgReceive = ReceiveMessageHandler(this) mMsgReceive = ReceiveMessageHandler(this)
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY) val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags()) ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())

View File

@@ -0,0 +1,94 @@
package com.v2ray.ang.service
import android.content.Context
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import java.io.File
/**
* Manages the tun2socks process that handles VPN traffic
*/
class TProxyService(
private val context: Context,
private val vpnInterface: ParcelFileDescriptor,
private val isRunningProvider: () -> Boolean,
private val restartCallback: () -> Unit
) : Tun2SocksControl {
companion object {
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyStartService(configPath: String, fd: Int)
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyStopService()
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyGetStats(): LongArray?
init {
System.loadLibrary("hev-socks5-tunnel")
}
}
/**
* Starts the tun2socks process with the appropriate parameters.
*/
override fun startTun2Socks() {
Log.i(AppConfig.TAG, "Starting HevSocks5Tunnel via JNI")
val configContent = buildConfig()
val configFile = File(context.filesDir, "hev-socks5-tunnel.yaml").apply {
writeText(configContent)
}
Log.i(AppConfig.TAG, "Config file created: ${configFile.absolutePath}")
Log.d(AppConfig.TAG, "Config content:\n$configContent")
try {
Log.i(AppConfig.TAG, "TProxyStartService...")
TProxyStartService(configFile.absolutePath, vpnInterface.fd)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "HevSocks5Tunnel exception: ${e.message}")
}
}
private fun buildConfig(): String {
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
return buildString {
appendLine("tunnel:")
appendLine(" mtu: ${SettingsManager.getVpnMtu()}")
appendLine(" ipv4: ${vpnConfig.ipv4Client}")
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
appendLine(" ipv6: '${vpnConfig.ipv6Client}'")
}
appendLine("socks5:")
appendLine(" port: ${socksPort}")
appendLine(" address: ${AppConfig.LOOPBACK}")
appendLine(" udp: 'udp'")
appendLine("misc:")
appendLine(" read-write-timeout: ${MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT) ?: AppConfig.HEVTUN_RW_TIMEOUT}")
val hevTunLogLevel = MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) ?: "none"
if (hevTunLogLevel != "none") {
appendLine(" log-level: $hevTunLogLevel")
}
}
}
/**
* Stops the tun2socks process
*/
override fun stopTun2Socks() {
try {
Log.i(AppConfig.TAG, "TProxyStopService...")
TProxyStopService()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to stop hev-socks5-tunnel", e)
}
}
}

View File

@@ -0,0 +1,19 @@
package com.v2ray.ang.service
/**
* Interface that defines the control operations for tun2socks implementations.
*
* This interface is implemented by different tunnel solutions like:
*/
interface Tun2SocksControl {
/**
* Starts the tun2socks process with the appropriate parameters.
* This initializes the VPN tunnel and connects it to the SOCKS proxy.
*/
fun startTun2Socks()
/**
* Stops the tun2socks process and cleans up resources.
*/
fun stopTun2Socks()
}

View File

@@ -0,0 +1,128 @@
package com.v2ray.ang.service
import android.content.Context
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
/**
* Manages the tun2socks process that handles VPN traffic
*/
class Tun2SocksService(
private val context: Context,
private val vpnInterface: ParcelFileDescriptor,
private val isRunningProvider: () -> Boolean,
private val restartCallback: () -> Unit
) : Tun2SocksControl {
companion object {
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var process: Process
/**
* Starts the tun2socks process with the appropriate parameters.
*/
override fun startTun2Socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val cmd = arrayListOf(
File(context.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", vpnConfig.ipv4Router,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "${AppConfig.LOOPBACK}:${socksPort}",
"--tunmtu", SettingsManager.getVpnMtu().toString(),
"--sock-path", "sock_path",
"--enable-udprelay",
"--loglevel", "notice"
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(vpnConfig.ipv6Router)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
)
cmd.add("--dnsgw")
cmd.add("${AppConfig.LOOPBACK}:${localDnsPort}")
}
Log.i(AppConfig.TAG, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(context.filesDir)
.start()
Thread {
Log.i(AppConfig.TAG, "$TUN2SOCKS check")
process.waitFor()
Log.i(AppConfig.TAG, "$TUN2SOCKS exited")
if (isRunningProvider()) {
Log.i(AppConfig.TAG, "$TUN2SOCKS restart")
restartCallback()
}
}.start()
Log.i(AppConfig.TAG, "$TUN2SOCKS process info: $process")
sendFd()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e)
}
}
/**
* Sends the file descriptor to the tun2socks process.
* Attempts to send the file descriptor multiple times if necessary.
*/
private fun sendFd() {
val fd = vpnInterface.fileDescriptor
val path = File(context.filesDir, "sock_path").absolutePath
Log.i(AppConfig.TAG, "LocalSocket path: $path")
CoroutineScope(Dispatchers.IO).launch {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
localSocket.outputStream.write(42)
}
break
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e)
if (tries > 5) break
tries += 1
}
}
}
/**
* Stops the tun2socks process
*/
override fun stopTun2Socks() {
try {
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy")
if (::process.isInitialized) {
process.destroy()
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
}
}

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
import java.lang.ref.SoftReference import java.lang.ref.SoftReference

View File

@@ -9,10 +9,10 @@ import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.serializable import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.PluginServiceManager
import com.v2ray.ang.handler.SpeedtestManager import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.V2rayConfigManager import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import go.Seq import go.Seq
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -78,7 +78,7 @@ class V2RayTestService : Service() {
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (config.configType == EConfigType.HYSTERIA2) { if (config.configType == EConfigType.HYSTERIA2) {
val delay = PluginUtil.realPingHy2(this, config) val delay = PluginServiceManager.realPingHy2(this, config)
return delay return delay
} else { } else {
val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(this, guid) val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(this, guid)

View File

@@ -5,8 +5,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
@@ -21,29 +19,17 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.NotificationManager
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl { class V2RayVpnService : VpnService(), ServiceControl {
companion object {
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "10.10.14.1"
private const val PRIVATE_VLAN4_ROUTER = "10.10.14.2"
private const val PRIVATE_VLAN6_CLIENT = "fc00::10:10:14:1"
private const val PRIVATE_VLAN6_ROUTER = "fc00::10:10:14:2"
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var mInterface: ParcelFileDescriptor private lateinit var mInterface: ParcelFileDescriptor
private var isRunning = false private var isRunning = false
private lateinit var process: Process private var tun2SocksService: Tun2SocksControl? = null
/**destroy /**destroy
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e * Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
@@ -100,7 +86,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
NotificationService.cancelNotification() NotificationManager.cancelNotification()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -116,7 +102,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
override fun startService() { override fun startService() {
setup() setupService()
} }
override fun stopService() { override fun stopService() {
@@ -139,13 +125,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Sets up the VPN service. * Sets up the VPN service.
* Prepares the VPN and configures it if preparation is successful. * Prepares the VPN and configures it if preparation is successful.
*/ */
private fun setup() { private fun setupService() {
val prepare = prepare(this) val prepare = prepare(this)
if (prepare != null) { if (prepare != null) {
return return
} }
if (setupVpnService() != true) { if (configureVpnService() != true) {
return return
} }
@@ -156,18 +142,54 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Configures the VPN service. * Configures the VPN service.
* @return True if the VPN service was configured successfully, false otherwise. * @return True if the VPN service was configured successfully, false otherwise.
*/ */
private fun setupVpnService(): Boolean { private fun configureVpnService(): Boolean {
// If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters.
val builder = Builder() val builder = Builder()
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
builder.setMtu(VPN_MTU) // Configure network settings (addresses, routing and DNS)
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30) configureNetworkSettings(builder)
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
// Configure app-specific settings (session name and per-app proxy)
configurePerAppProxy(builder)
// Close the old interface since the parameters have been changed
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
// Configure platform-specific features
configurePlatformFeatures(builder)
// Create a new interface using the builder and save the parameters
try {
mInterface = builder.establish()!!
isRunning = true
return true
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e)
stopV2Ray()
}
return false
}
/**
* Configures the basic network settings for the VPN.
* This includes IP addresses, routing rules, and DNS servers.
*
* @param builder The VPN Builder to configure
*/
private fun configureNetworkSettings(builder: Builder) {
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val bypassLan = SettingsManager.routingRulesetsBypassLan() val bypassLan = SettingsManager.routingRulesetsBypassLan()
// Configure IPv4 settings
builder.setMtu(SettingsManager.getVpnMtu())
builder.addAddress(vpnConfig.ipv4Client, 30)
// Configure routing rules
if (bypassLan) { if (bypassLan) {
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach { AppConfig.ROUTED_IP_LIST.forEach {
val addr = it.split('/') val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt()) builder.addRoute(addr[0], addr[1].toInt())
} }
@@ -175,55 +197,37 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.addRoute("0.0.0.0", 0) builder.addRoute("0.0.0.0", 0)
} }
// Configure IPv6 if enabled
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) { if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126) builder.addAddress(vpnConfig.ipv6Client, 126)
if (bypassLan) { if (bypassLan) {
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use builder.addRoute("2000::", 3) // Currently only 1/8 of total IPv6 is in use
builder.addRoute("fc00::", 18) // Xray-core default FakeIPv6 Pool
} else { } else {
builder.addRoute("::", 0) builder.addRoute("::", 0)
} }
} }
// if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) { // Configure DNS servers
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER) //if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// } else { // builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
SettingsManager.getVpnDnsServers() //} else {
.forEach { SettingsManager.getVpnDnsServers().forEach {
if (Utils.isPureIpAddress(it)) { if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it) builder.addDnsServer(it)
}
} }
// } }
builder.setSession(V2RayServiceManager.getRunningServerName()) builder.setSession(V2RayServiceManager.getRunningServerName())
}
val selfPackageName = BuildConfig.APPLICATION_ID /**
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)) { * Configures platform-specific VPN features for different Android versions.
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET) *
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS) * @param builder The VPN Builder to configure
//process self package */
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName) private fun configurePlatformFeatures(builder: Builder) {
apps?.forEach { // Android P (API 28) and above: Configure network callbacks
try {
if (bypassApps)
builder.addDisallowedApplication(it)
else
builder.addAllowedApplication(it)
} catch (e: PackageManager.NameNotFoundException) {
Log.e(AppConfig.TAG, "Failed to configure app in VPN: ${e.localizedMessage}", e)
}
}
} else {
builder.addDisallowedApplication(selfPackageName)
}
// Close the old interface since the parameters have been changed.
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try { try {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback) connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
@@ -232,24 +236,58 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
// Android Q (API 29) and above: Configure metering and HTTP proxy
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false) builder.setMetered(false)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY)) { if (MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY)) {
builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOOPBACK, SettingsManager.getHttpPort())) builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOOPBACK, SettingsManager.getHttpPort()))
} }
} }
}
// Create a new interface using the builder and save the parameters. /**
try { * Configures per-app proxy rules for the VPN builder.
mInterface = builder.establish()!! *
isRunning = true * - If per-app proxy is not enabled, disallow the VPN service's own package.
return true * - If no apps are selected, disallow the VPN service's own package.
} catch (e: Exception) { * - If bypass mode is enabled, disallow all selected apps (including self).
// non-nullable lateinit var * - If proxy mode is enabled, only allow the selected apps (excluding self).
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e) *
stopV2Ray() * @param builder The VPN Builder to configure.
*/
private fun configurePerAppProxy(builder: Builder) {
val selfPackageName = BuildConfig.APPLICATION_ID
// If per-app proxy is not enabled, disallow the VPN service's own package and return
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY) == false) {
builder.addDisallowedApplication(selfPackageName)
return
}
// If no apps are selected, disallow the VPN service's own package and return
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
if (apps.isNullOrEmpty()) {
builder.addDisallowedApplication(selfPackageName)
return
}
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
// Handle the VPN service's own package according to the mode
if (bypassApps) apps.add(selfPackageName) else apps.remove(selfPackageName)
apps.forEach {
try {
if (bypassApps) {
// In bypass mode, disallow the selected apps
builder.addDisallowedApplication(it)
} else {
// In proxy mode, only allow the selected apps
builder.addAllowedApplication(it)
}
} catch (e: PackageManager.NameNotFoundException) {
Log.e(AppConfig.TAG, "Failed to configure app in VPN: ${e.localizedMessage}", e)
}
} }
return false
} }
/** /**
@@ -257,79 +295,23 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Starts the tun2socks process with the appropriate parameters. * Starts the tun2socks process with the appropriate parameters.
*/ */
private fun runTun2socks() { private fun runTun2socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS") if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL) == true) {
val socksPort = SettingsManager.getSocksPort() tun2SocksService = TProxyService(
val cmd = arrayListOf( context = applicationContext,
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath, vpnInterface = mInterface,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER, isRunningProvider = { isRunning },
"--netif-netmask", "255.255.255.252", restartCallback = { runTun2socks() }
"--socks-server-addr", "$LOOPBACK:${socksPort}", )
"--tunmtu", VPN_MTU.toString(), } else {
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath, tun2SocksService = Tun2SocksService(
"--enable-udprelay", context = applicationContext,
"--loglevel", "notice" vpnInterface = mInterface,
) isRunningProvider = { isRunning },
restartCallback = { runTun2socks() }
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) { )
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
} }
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw")
cmd.add("$LOOPBACK:${localDnsPort}")
}
Log.i(AppConfig.TAG, cmd.toString())
try { tun2SocksService?.startTun2Socks()
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(applicationContext.filesDir)
.start()
Thread {
Log.i(AppConfig.TAG, "$TUN2SOCKS check")
process.waitFor()
Log.i(AppConfig.TAG, "$TUN2SOCKS exited")
if (isRunning) {
Log.i(AppConfig.TAG, "$TUN2SOCKS restart")
runTun2socks()
}
}.start()
Log.i(AppConfig.TAG, "$TUN2SOCKS process info : ${process.toString()}")
sendFd()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e)
}
}
/**
* Sends the file descriptor to the tun2socks process.
* Attempts to send the file descriptor multiple times if necessary.
*/
private fun sendFd() {
val fd = mInterface.fileDescriptor
val path = File(applicationContext.filesDir, "sock_path").absolutePath
Log.i(AppConfig.TAG, "LocalSocket path : $path")
CoroutineScope(Dispatchers.IO).launch {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
localSocket.outputStream.write(42)
}
break
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e)
if (tries > 5) break
tries += 1
}
}
} }
/** /**
@@ -350,12 +332,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
try { tun2SocksService?.stopTun2Socks()
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy") tun2SocksService = null
process.destroy()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
V2RayServiceManager.stopCoreLoop() V2RayServiceManager.stopCoreLoop()
@@ -375,3 +353,4 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
} }

View File

@@ -5,28 +5,21 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding import com.v2ray.ang.databinding.ActivityAboutBinding
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.UpdateCheckerManager
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil import com.v2ray.ang.util.ZipUtil
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@@ -105,23 +98,6 @@ class AboutActivity : BaseActivity() {
} }
} }
//If it is the Google Play version, not be displayed within 1 days after update
if (Utils.isGoogleFlavor()) {
val lastUpdateTime = AppManagerUtil.getLastUpdateTime(this)
val currentTime = System.currentTimeMillis()
if ((currentTime - lastUpdateTime) < 1 * 24 * 60 * 60 * 1000L) {
binding.layoutCheckUpdate.visibility = View.GONE
}
}
binding.layoutCheckUpdate.setOnClickListener {
checkForUpdates(binding.checkPreRelease.isChecked)
}
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
}
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
binding.layoutSoureCcode.setOnClickListener { binding.layoutSoureCcode.setOnClickListener {
Utils.openUri(this, AppConfig.APP_URL) Utils.openUri(this, AppConfig.APP_URL)
} }
@@ -222,28 +198,4 @@ class AboutActivity : BaseActivity() {
} }
} }
} }
private fun checkForUpdates(includePreRelease: Boolean) {
lifecycleScope.launch {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toast(R.string.update_already_latest_version)
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(result.releaseNotes)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
} }

View File

@@ -0,0 +1,77 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityCheckUpdateBinding
import com.v2ray.ang.dto.CheckUpdateResult
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.UpdateCheckerManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.launch
class CheckUpdateActivity : BaseActivity() {
private val binding by lazy { ActivityCheckUpdateBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
title = getString(R.string.update_check_for_update)
binding.layoutCheckUpdate.setOnClickListener {
checkForUpdates(binding.checkPreRelease.isChecked)
}
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
}
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
"v${BuildConfig.VERSION_NAME} (${SpeedtestManager.getLibVersion()})".also {
binding.tvVersion.text = it
}
checkForUpdates(binding.checkPreRelease.isChecked)
}
private fun checkForUpdates(includePreRelease: Boolean) {
toast(R.string.update_checking_for_update)
lifecycleScope.launch {
try {
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
if (result.hasUpdate) {
showUpdateDialog(result)
} else {
toastSuccess(R.string.update_already_latest_version)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
toastError(e.message ?: getString(R.string.toast_failure))
}
}
}
private fun showUpdateDialog(result: CheckUpdateResult) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(result.releaseNotes)
.setPositiveButton(R.string.update_now) { _, _ ->
result.downloadUrl?.let {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}

View File

@@ -38,7 +38,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MigrateManager import com.v2ray.ang.handler.MigrateManager
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -385,6 +385,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true true
} }
R.id.intelligent_selection_all -> {
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") != "0") {
toast(getString(R.string.pre_resolving_domain))
}
mainViewModel.createIntelligentSelectionAll()
true
}
R.id.service_restart -> { R.id.service_restart -> {
restartV2Ray() restartV2Ray()
true true
@@ -685,6 +693,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}") R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}")
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java)) R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
R.id.check_for_update -> startActivity(Intent(this, CheckUpdateActivity::class.java))
R.id.about -> startActivity(Intent(this, AboutActivity::class.java)) R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
} }

View File

@@ -26,7 +26,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -56,8 +56,14 @@ class PerAppProxyActivity : BaseActivity() {
appsList.sortedWith { p1, p2 -> appsList.sortedWith { p1, p2 ->
when { when {
p1.isSelected > p2.isSelected -> -1 p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0 p1.isSelected < p2.isSelected -> 1
else -> 1 p1.isSystemApp > p2.isSystemApp -> 1
p1.isSystemApp < p2.isSystemApp -> -1
p1.appName.lowercase() > p2.appName.lowercase() -> 1
p1.appName.lowercase() < p2.appName.lowercase() -> -1
p1.packageName > p2.packageName -> 1
p1.packageName < p2.packageName -> -1
else -> 0
} }
} }
} else { } else {

View File

@@ -2,7 +2,7 @@ package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.handler.V2RayServiceManager
class ScSwitchActivity : BaseActivity() { class ScSwitchActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -117,6 +117,8 @@ class ServerActivity : BaseActivity() {
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) } private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) } private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) } private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) }
private val et_mldsa65_verify: EditText? by lazy { findViewById(R.id.et_mldsa65_verify) }
private val container_mldsa65_verify: LinearLayout? by lazy { findViewById(R.id.lay_mldsa65_verify) }
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) } private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) } private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) } private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
@@ -253,9 +255,14 @@ class ServerActivity : BaseActivity() {
// Case 1: Null or blank // Case 1: Null or blank
isBlank -> { isBlank -> {
listOf( listOf(
container_sni, container_fingerprint, container_alpn, container_sni,
container_allow_insecure, container_public_key, container_fingerprint,
container_short_id, container_spider_x container_alpn,
container_allow_insecure,
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE } ).forEach { it?.visibility = View.GONE }
} }
@@ -270,7 +277,8 @@ class ServerActivity : BaseActivity() {
listOf( listOf(
container_public_key, container_public_key,
container_short_id, container_short_id,
container_spider_x container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE } ).forEach { it?.visibility = View.GONE }
} }
@@ -284,7 +292,8 @@ class ServerActivity : BaseActivity() {
listOf( listOf(
container_public_key, container_public_key,
container_short_id, container_short_id,
container_spider_x container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.VISIBLE } ).forEach { it?.visibility = View.VISIBLE }
} }
} }
@@ -366,9 +375,12 @@ class ServerActivity : BaseActivity() {
if (allowinsecure >= 0) { if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure) sp_allow_insecure?.setSelection(allowinsecure)
} }
container_public_key?.visibility = View.GONE listOf(
container_short_id?.visibility = View.GONE container_public_key,
container_spider_x?.visibility = View.GONE container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
} else if (config.security == REALITY) { } else if (config.security == REALITY) {
container_public_key?.visibility = View.VISIBLE container_public_key?.visibility = View.VISIBLE
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty()) et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
@@ -376,18 +388,23 @@ class ServerActivity : BaseActivity() {
et_short_id?.text = Utils.getEditable(config.shortId.orEmpty()) et_short_id?.text = Utils.getEditable(config.shortId.orEmpty())
container_spider_x?.visibility = View.VISIBLE container_spider_x?.visibility = View.VISIBLE
et_spider_x?.text = Utils.getEditable(config.spiderX.orEmpty()) et_spider_x?.text = Utils.getEditable(config.spiderX.orEmpty())
container_mldsa65_verify?.visibility = View.VISIBLE
et_mldsa65_verify?.text = Utils.getEditable(config.mldsa65Verify.orEmpty())
container_allow_insecure?.visibility = View.GONE container_allow_insecure?.visibility = View.GONE
} }
} }
if (config.security.isNullOrEmpty()) { if (config.security.isNullOrEmpty()) {
container_sni?.visibility = View.GONE listOf(
container_fingerprint?.visibility = View.GONE container_sni,
container_alpn?.visibility = View.GONE container_fingerprint,
container_allow_insecure?.visibility = View.GONE container_alpn,
container_public_key?.visibility = View.GONE container_allow_insecure,
container_short_id?.visibility = View.GONE container_public_key,
container_spider_x?.visibility = View.GONE container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
} }
val network = Utils.arrayFind(networks, config.network.orEmpty()) val network = Utils.arrayFind(networks, config.network.orEmpty())
if (network >= 0) { if (network >= 0) {
@@ -550,6 +567,7 @@ class ServerActivity : BaseActivity() {
val publicKey = et_public_key?.text?.toString() val publicKey = et_public_key?.text?.toString()
val shortId = et_short_id?.text?.toString() val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString() val spiderX = et_spider_x?.text?.toString()
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
val allowInsecure = val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) { if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
@@ -566,6 +584,7 @@ class ServerActivity : BaseActivity() {
config.publicKey = publicKey config.publicKey = publicKey
config.shortId = shortId config.shortId = shortId
config.spiderX = spiderX config.spiderX = spiderX
config.mldsa65Verify = mldsa65Verify
} }
private fun transportTypes(network: String?): Array<out String> { private fun transportTypes(network: String?): Array<out String> {

View File

@@ -18,7 +18,7 @@ import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toLongEx import com.v2ray.ang.extension.toLongEx
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.SubscriptionUpdater import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel import com.v2ray.ang.viewmodel.SettingsViewModel
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -44,6 +44,8 @@ class SettingsActivity : BaseActivity() {
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) } private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) } private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) } private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
private val vpnInterfaceAddress by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX) }
private val vpnMtu by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_MTU) }
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) } private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) } private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
@@ -65,6 +67,10 @@ class SettingsActivity : BaseActivity() {
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) } private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) } private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
private val hevTunLogLevel by lazy { findPreference<ListPreference>(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) }
private val hevTunRwTimeout by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT) }
private val useHevTun by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_USE_HEV_TUNNEL) }
override fun onCreatePreferences(bundle: Bundle?, s: String?) { override fun onCreatePreferences(bundle: Bundle?, s: String?) {
addPreferencesFromResource(R.xml.pref_settings) addPreferencesFromResource(R.xml.pref_settings)
@@ -88,6 +94,12 @@ class SettingsActivity : BaseActivity() {
true true
} }
vpnMtu?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
vpnMtu?.summary = if (TextUtils.isEmpty(nval)) AppConfig.VPN_MTU.toString() else nval
true
}
mux?.setOnPreferenceChangeListener { _, newValue -> mux?.setOnPreferenceChangeListener { _, newValue ->
updateMux(newValue as Boolean) updateMux(newValue as Boolean)
true true
@@ -171,6 +183,16 @@ class SettingsActivity : BaseActivity() {
mode?.dialogLayoutResource = R.layout.preference_with_help_link mode?.dialogLayoutResource = R.layout.preference_with_help_link
//loglevel.summary = "LogLevel" //loglevel.summary = "LogLevel"
useHevTun?.setOnPreferenceChangeListener { _, newValue ->
updateHevTunSettings(newValue as Boolean)
true
}
hevTunRwTimeout?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
hevTunRwTimeout?.summary = if (TextUtils.isEmpty(nval)) AppConfig.HEVTUN_RW_TIMEOUT else nval
true
}
} }
override fun onStart() { override fun onStart() {
@@ -181,6 +203,7 @@ class SettingsActivity : BaseActivity() {
appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false) appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false)
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN) vpnDns?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_DNS, AppConfig.DNS_VPN)
vpnMtu?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_MTU, AppConfig.VPN_MTU.toString())
updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)) updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false))
mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false) mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
@@ -204,6 +227,9 @@ class SettingsActivity : BaseActivity() {
dnsHosts?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS) dnsHosts?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DELAY_TEST_URL) delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DELAY_TEST_URL)
updateHevTunSettings(MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL, false))
hevTunRwTimeout?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT, AppConfig.HEVTUN_RW_TIMEOUT)
initSharedPreference() initSharedPreference()
} }
@@ -211,6 +237,7 @@ class SettingsActivity : BaseActivity() {
listOf( listOf(
localDnsPort, localDnsPort,
vpnDns, vpnDns,
vpnMtu,
muxConcurrency, muxConcurrency,
muxXudpConcurrency, muxXudpConcurrency,
fragmentLength, fragmentLength,
@@ -219,7 +246,8 @@ class SettingsActivity : BaseActivity() {
socksPort, socksPort,
remoteDns, remoteDns,
domesticDns, domesticDns,
delayTestUrl delayTestUrl,
hevTunRwTimeout
).forEach { key -> ).forEach { key ->
key?.text = key?.summary.toString() key?.text = key?.summary.toString()
} }
@@ -241,7 +269,8 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, AppConfig.PREF_DOUBLE_COLUMN_DISPLAY,
AppConfig.PREF_PREFER_IPV6, AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PROXY_SHARING, AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_ALLOW_INSECURE AppConfig.PREF_ALLOW_INSECURE,
AppConfig.PREF_USE_HEV_TUNNEL
).forEach { key -> ).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked = findPreference<CheckBoxPreference>(key)?.isChecked =
MmkvManager.decodeSettingsBool(key, false) MmkvManager.decodeSettingsBool(key, false)
@@ -249,13 +278,17 @@ class SettingsActivity : BaseActivity() {
listOf( listOf(
AppConfig.PREF_VPN_BYPASS_LAN, AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_MUX_XUDP_QUIC, AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_FRAGMENT_PACKETS, AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_LANGUAGE, AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT, AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_LOGLEVEL, AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_MODE AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_INTELLIGENT_SELECTION_METHOD,
AppConfig.PREF_MODE,
AppConfig.PREF_HEV_TUNNEL_LOGLEVEL
).forEach { key -> ).forEach { key ->
if (MmkvManager.decodeSettingsString(key) != null) { if (MmkvManager.decodeSettingsString(key) != null) {
findPreference<ListPreference>(key)?.value = MmkvManager.decodeSettingsString(key) findPreference<ListPreference>(key)?.value = MmkvManager.decodeSettingsString(key)
@@ -273,7 +306,8 @@ class SettingsActivity : BaseActivity() {
localDnsPort?.isEnabled = vpn localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn vpnDns?.isEnabled = vpn
vpnBypassLan?.isEnabled = vpn vpnBypassLan?.isEnabled = vpn
vpn vpnInterfaceAddress?.isEnabled = vpn
vpnMtu?.isEnabled = vpn
if (vpn) { if (vpn) {
updateLocalDns( updateLocalDns(
MmkvManager.decodeSettingsBool( MmkvManager.decodeSettingsBool(
@@ -361,6 +395,11 @@ class SettingsActivity : BaseActivity() {
private fun updateFragmentInterval(value: String?) { private fun updateFragmentInterval(value: String?) {
fragmentInterval?.summary = value.toString() fragmentInterval?.summary = value.toString()
} }
private fun updateHevTunSettings(enabled: Boolean) {
hevTunLogLevel?.isEnabled = enabled
hevTunRwTimeout?.isEnabled = enabled
}
} }
fun onModeHelpClicked(view: View) { fun onModeHelpClicked(view: View) {

View File

@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.dto.SubscriptionItem
@@ -44,6 +45,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = Utils.getEditable(subItem.remarks) binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url) binding.etUrl.text = Utils.getEditable(subItem.url)
binding.etFilter.text = Utils.getEditable(subItem.filter) binding.etFilter.text = Utils.getEditable(subItem.filter)
binding.etIntelligentSelectionFilter.text = Utils.getEditable(subItem.intelligentSelectionFilter)
binding.chkEnable.isChecked = subItem.enabled binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate binding.autoUpdateCheck.isChecked = subItem.autoUpdate
binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl
@@ -59,6 +61,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = null binding.etRemarks.text = null
binding.etUrl.text = null binding.etUrl.text = null
binding.etFilter.text = null binding.etFilter.text = null
binding.etIntelligentSelectionFilter.text = null
binding.chkEnable.isChecked = true binding.chkEnable.isChecked = true
binding.etPreProfile.text = null binding.etPreProfile.text = null
binding.etNextProfile.text = null binding.etNextProfile.text = null
@@ -74,6 +77,7 @@ class SubEditActivity : BaseActivity() {
subItem.remarks = binding.etRemarks.text.toString() subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString() subItem.url = binding.etUrl.text.toString()
subItem.filter = binding.etFilter.text.toString() subItem.filter = binding.etFilter.text.toString()
subItem.intelligentSelectionFilter = binding.etIntelligentSelectionFilter.text.toString()
subItem.enabled = binding.chkEnable.isChecked subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked subItem.autoUpdate = binding.autoUpdateCheck.isChecked
subItem.prevProfile = binding.etPreProfile.text.toString() subItem.prevProfile = binding.etPreProfile.text.toString()
@@ -109,19 +113,28 @@ class SubEditActivity : BaseActivity() {
*/ */
private fun deleteServer(): Boolean { private fun deleteServer(): Boolean {
if (editSubId.isNotEmpty()) { if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
.setPositiveButton(android.R.string.ok) { _, _ -> AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
lifecycleScope.launch(Dispatchers.IO) { .setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeSubscription(editSubId) lifecycleScope.launch(Dispatchers.IO) {
launch(Dispatchers.Main) { MmkvManager.removeSubscription(editSubId)
finish() launch(Dispatchers.Main) {
finish()
}
} }
} }
.setNegativeButton(android.R.string.cancel) { _, _ ->
// do nothing
}
.show()
} else {
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
}
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
// do nothing
}
.show()
} }
return true return true
} }

View File

@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
@@ -20,6 +21,8 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.QRCodeDecoder import com.v2ray.ang.util.QRCodeDecoder
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter { class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
@@ -46,6 +49,10 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
) )
} }
holder.itemSubSettingBinding.layoutRemove.setOnClickListener {
removeSubscription(subId, position)
}
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked -> holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if (!it.isPressed) return@setOnCheckedChangeListener if (!it.isPressed) return@setOnCheckedChangeListener
subItem.enabled = isChecked subItem.enabled = isChecked
@@ -54,9 +61,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
} }
if (TextUtils.isEmpty(subItem.url)) { if (TextUtils.isEmpty(subItem.url)) {
holder.itemSubSettingBinding.layoutUrl.visibility = View.GONE
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE
} else { } else {
holder.itemSubSettingBinding.layoutUrl.visibility = View.VISIBLE
holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE
holder.itemSubSettingBinding.layoutShare.setOnClickListener { holder.itemSubSettingBinding.layoutShare.setOnClickListener {
@@ -90,6 +99,32 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
} }
} }
private fun removeSubscription(subId: String, position: Int) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
removeSubscriptionSub(subId, position)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
} else {
removeSubscriptionSub(subId, position)
}
}
private fun removeSubscriptionSub(subId: String, position: Int) {
mActivity.lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(subId)
launch(Dispatchers.Main) {
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.subscriptions.size)
mActivity.refreshData()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return MainViewHolder( return MainViewHolder(
ItemRecyclerSubSettingBinding.inflate( ItemRecyclerSubSettingBinding.inflate(

View File

@@ -12,18 +12,22 @@ import java.net.IDN
import java.net.Inet6Address import java.net.Inet6Address
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.MalformedURLException
import java.net.Proxy import java.net.Proxy
import java.net.URI
import java.net.URL import java.net.URL
object HttpUtil { object HttpUtil {
/** /**
* Converts a URL string to its ASCII representation. * Converts the domain part of a URL string to its IDN (Punycode, ASCII Compatible Encoding) format.
* *
* @param str The URL string to convert. * For example, a URL like "https://例子.中国/path" will be converted to "https://xn--fsqu00a.xn--fiqs8s/path".
* @return The ASCII representation of the URL. *
* @param str The URL string to convert (can contain non-ASCII characters in the domain).
* @return The URL string with the domain part converted to ASCII-compatible (Punycode) format.
*/ */
fun idnToASCII(str: String): String { fun toIdnUrl(str: String): String {
val url = URL(str) val url = URL(str)
val host = url.host val host = url.host
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED) val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
@@ -34,6 +38,28 @@ object HttpUtil {
} }
} }
/**
* Converts a Unicode domain name to its IDN (Punycode, ASCII Compatible Encoding) format.
* If the input is an IP address or already an ASCII domain, returns the original string.
*
* @param domain The domain string to convert (can include non-ASCII internationalized characters).
* @return The domain in ASCII-compatible (Punycode) format, or the original string if input is an IP or already ASCII.
*/
fun toIdnDomain(domain: String): String {
// Return as is if it's a pure IP address (IPv4 or IPv6)
if (Utils.isPureIpAddress(domain)) {
return domain
}
// Return as is if already ASCII (English domain or already punycode)
if (domain.all { it.code < 128 }) {
return domain
}
// Otherwise, convert to ASCII using IDN
return IDN.toASCII(domain, IDN.ALLOW_UNASSIGNED)
}
/** /**
* Resolves a hostname to an IP address, returns original input if it's already an IP * Resolves a hostname to an IP address, returns original input if it's already an IP
* *
@@ -116,7 +142,7 @@ object HttpUtil {
val responseCode = conn.responseCode val responseCode = conn.responseCode
when (responseCode) { when (responseCode) {
in 300..399 -> { in 300..399 -> {
val location = conn.getHeaderField("Location") val location = resolveLocation(conn)
conn.disconnect() conn.disconnect()
if (location.isNullOrEmpty()) { if (location.isNullOrEmpty()) {
throw IOException("Redirect location not found") throw IOException("Redirect location not found")
@@ -195,5 +221,29 @@ object HttpUtil {
} }
return conn return conn
} }
// Returns absolute URL string location header sets
fun resolveLocation(conn: HttpURLConnection): String? {
val raw = conn.getHeaderField("Location")?.trim()?.takeIf { it.isNotEmpty() } ?: return null
// Try check url is relative or absolute
return try {
val locUri = URI(raw)
val baseUri = conn.url.toURI()
val resolved = if (locUri.isAbsolute) locUri else baseUri.resolve(locUri)
resolved.toURL().toString()
} catch (_: Exception) {
// Fallback: url resolver, also should handles //host/...
try {
URL(raw).toString() // absolute with protocol
} catch (_: MalformedURLException) {
try {
URL(conn.url, raw).toString()
} catch (_: MalformedURLException) {
null
}
}
}
}
} }

View File

@@ -198,6 +198,21 @@ object Utils {
return isIpv4Address(value) || isIpv6Address(value) return isIpv4Address(value) || isIpv6Address(value)
} }
/**
* Check if a string is a valid domain name.
*
* A valid domain name must not be an IP address and must be a valid URL format.
*
* @param input The string to check.
* @return True if the string is a valid domain name, false otherwise.
*/
fun isDomainName(input: String?): Boolean {
if (input.isNullOrEmpty()) return false
// Must not be an IP address and must be a valid URL format
return !isPureIpAddress(input) && isValidUrl(input)
}
/** /**
* Check if a string is a valid IPv4 address. * Check if a string is a valid IPv4 address.
* *

View File

@@ -384,6 +384,29 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MmkvManager.encodeServerList(serverList) MmkvManager.encodeServerList(serverList)
} }
/**
* Creates an intelligent selection configuration containing all currently filtered servers.
*/
fun createIntelligentSelectionAll() {
viewModelScope.launch(Dispatchers.IO) {
val key = AngConfigManager.createIntelligentSelection(
getApplication<AngApplication>(),
serversCache.map { it.guid }.toList(),
subscriptionId
)
launch(Dispatchers.Main) {
if (key.isNullOrEmpty()) {
getApplication<AngApplication>().toastError(R.string.toast_failure)
} else {
getApplication<AngApplication>().toastSuccess(R.string.toast_success)
MmkvManager.setSelectServer(key)
reloadServerList()
}
}
}
}
/** /**
* Initializes assets. * Initializes assets.
* @param assets The asset manager. * @param assets The asset manager.

View File

@@ -41,6 +41,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_MODE, AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS, AppConfig.PREF_VPN_DNS,
AppConfig.PREF_VPN_BYPASS_LAN, AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_VPN_MTU,
AppConfig.PREF_REMOTE_DNS, AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS, AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_DNS_HOSTS, AppConfig.PREF_DNS_HOSTS,
@@ -48,6 +50,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PREF_LOCAL_DNS_PORT,
AppConfig.PREF_SOCKS_PORT, AppConfig.PREF_SOCKS_PORT,
AppConfig.PREF_LOGLEVEL, AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_INTELLIGENT_SELECTION_METHOD,
AppConfig.PREF_LANGUAGE, AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT, AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
@@ -56,6 +60,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_FRAGMENT_LENGTH, AppConfig.PREF_FRAGMENT_LENGTH,
AppConfig.PREF_FRAGMENT_INTERVAL, AppConfig.PREF_FRAGMENT_INTERVAL,
AppConfig.PREF_MUX_XUDP_QUIC, AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_HEV_TUNNEL_LOGLEVEL,
AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT
-> { -> {
MmkvManager.encodeSettings(key, sharedPreferences.getString(key, "")) MmkvManager.encodeSettings(key, sharedPreferences.getString(key, ""))
} }
@@ -77,6 +83,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.SUBSCRIPTION_AUTO_UPDATE, AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_FRAGMENT_ENABLED, AppConfig.PREF_FRAGMENT_ENABLED,
AppConfig.PREF_MUX_ENABLED, AppConfig.PREF_MUX_ENABLED,
AppConfig.PREF_USE_HEV_TUNNEL
-> { -> {
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, false)) MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, false))
} }

View File

@@ -111,49 +111,6 @@
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="@dimen/padding_spacing_dp16"> android:paddingTop="@dimen/padding_spacing_dp16">
<LinearLayout
android:id="@+id/layout_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_check_update_24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/padding_spacing_dp16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update_check_for_update"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/check_pre_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:maxLines="1"
android:text="@string/update_check_pre_release"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_soure_ccode" android:id="@+id/layout_soure_ccode"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_source_code_24dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/check_pre_release"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingStart="@dimen/padding_spacing_dp16"
android:text="@string/update_check_pre_release"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorAccent"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_check_update_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/padding_spacing_dp16"
android:text="@string/update_check_for_update"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp16">
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_about"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -93,6 +93,25 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub_setting_intelligent_selection_filter" />
<EditText
android:id="@+id/et_intelligent_selection_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -15,97 +15,144 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:nextFocusRight="@+id/layout_edit" android:nextFocusRight="@+id/layout_share"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp8"> android:padding="@dimen/padding_spacing_dp8">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:orientation="vertical">
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8"
android:lines="2"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
android:id="@+id/layout_share" android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless" android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8"> android:paddingStart="@dimen/padding_spacing_dp8">
<ImageView <TextView
android:layout_width="@dimen/image_size_dp24" android:id="@+id/tv_name"
android:layout_height="@dimen/image_size_dp24" android:layout_width="wrap_content"
app:srcCompat="@drawable/ic_share_24dp" /> android:layout_height="wrap_content"
android:maxLines="2"
android:minLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center" android:gravity="center"
android:nextFocusLeft="@+id/info_container" android:orientation="horizontal">
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8"> <LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:nextFocusLeft="@+id/info_container"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_share_24dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_edit_24dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_delete_24dp" />
</LinearLayout>
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_edit_24dp" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:id="@+id/layout_url"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp8" android:orientation="horizontal"
android:orientation="horizontal"> android:paddingStart="@dimen/padding_spacing_dp8"
android:paddingEnd="@dimen/padding_spacing_dp8">
<androidx.appcompat.widget.SwitchCompat <LinearLayout
android:id="@+id/chk_enable" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingTop="@dimen/padding_spacing_dp8">
<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="2"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:theme="@style/BrandedSwitch" /> android:layout_marginTop="@dimen/padding_spacing_dp8"
android:orientation="horizontal">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_enable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:theme="@style/BrandedSwitch" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -178,4 +178,25 @@
android:nextFocusDown="@+id/sp_stream_fingerprint" /> android:nextFocusDown="@+id/sp_stream_fingerprint" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/lay_mldsa65_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_mldsa65_verify" />
<EditText
android:id="@+id/et_mldsa65_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:nextFocusDown="@+id/sp_stream_fingerprint" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -35,6 +35,10 @@
android:id="@+id/logcat" android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_24dp" android:icon="@drawable/ic_logcat_24dp"
android:title="@string/title_logcat" /> android:title="@string/title_logcat" />
<item
android:id="@+id/check_for_update"
android:icon="@drawable/ic_check_update_24dp"
android:title="@string/update_check_for_update" />
<item <item
android:id="@+id/about" android:id="@+id/about"
android:icon="@drawable/ic_about_24dp" android:icon="@drawable/ic_about_24dp"

View File

@@ -82,6 +82,10 @@
android:icon="@drawable/ic_share_24dp" android:icon="@drawable/ic_share_24dp"
android:title="@string/title_export_all" android:title="@string/title_export_all"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/intelligent_selection_all"
android:title="@string/title_create_intelligent_selection_all_server"
app:showAsAction="never" />
<item <item
android:id="@+id/ping_all" android:id="@+id/ping_all"
android:title="@string/title_ping_all_server" android:title="@string/title_ping_all_server"

View File

@@ -81,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string> <string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">المعرّف القصير</string> <string name="server_lab_short_id" translatable="false">المعرّف القصير</string>
<string name="server_lab_spider_x" translatable="false">SpiderX</string> <string name="server_lab_spider_x" translatable="false">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key" translatable="false">المفتاح السري</string> <string name="server_lab_secret_key" translatable="false">المفتاح السري</string>
<string name="server_lab_reserved">محجوز (اختياري)</string> <string name="server_lab_reserved">محجوز (اختياري)</string>
<string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string> <string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string>
@@ -141,6 +142,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">الإعدادات</string> <string name="title_settings">الإعدادات</string>
<string name="title_advanced">إعدادات متقدمة</string> <string name="title_advanced">إعدادات متقدمة</string>
<string name="title_core_settings">إعدادات النواة</string>
<string name="title_vpn_settings">إعدادات VPN</string> <string name="title_vpn_settings">إعدادات VPN</string>
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string> <string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string> <string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
@@ -181,6 +183,9 @@
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string> <string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string> <string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string> <string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +243,16 @@
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string> <string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string>
<string name="title_core_loglevel">مستوى السجل</string> <string name="title_core_loglevel">مستوى السجل</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">الوضع</string> <string name="title_mode">الوضع</string>
<string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string> <string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string>
<string name="title_language">اللغة</string> <string name="title_language">اللغة</string>
<string name="title_ui_settings">إعدادات واجهة المستخدم</string> <string name="title_ui_settings">إعدادات واجهة المستخدم</string>
<string name="title_pref_ui_mode_night">إعدادات وضع واجهة المستخدم ليلاً</string> <string name="title_pref_ui_mode_night">إعدادات وضع واجهة المستخدم ليلاً</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun Read/Write Timeout (ms, default 300000)</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">نسخ</string> <string name="logcat_copy">نسخ</string>
@@ -265,6 +275,7 @@
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string> <string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string> <string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string> <string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string> <string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string> <string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
<string name="title_filter_config">تصفية ملف التكوين</string> <string name="title_filter_config">تصفية ملف التكوين</string>
@@ -316,6 +327,7 @@
<string name="update_new_version_found">New version found: %s</string> <string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string> <string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string> <string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>رمز استجابة سريعة (QRcode)</item> <item>رمز استجابة سريعة (QRcode)</item>
@@ -353,4 +365,18 @@
<item>Not Bypass</item> <item>Not Bypass</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string> <string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">শর্ট আইডি</string> <string name="server_lab_short_id" translatable="false">শর্ট আইডি</string>
<string name="server_lab_spider_x" translatable="false">SpiderX</string> <string name="server_lab_spider_x" translatable="false">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key" translatable="false">সিক্রেট কী</string> <string name="server_lab_secret_key" translatable="false">সিক্রেট কী</string>
<string name="server_lab_reserved">সংরক্ষিত (ঐচ্ছিক)</string> <string name="server_lab_reserved">সংরক্ষিত (ঐচ্ছিক)</string>
<string name="server_lab_local_address">স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা)</string> <string name="server_lab_local_address">স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা)</string>
@@ -139,6 +140,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">সেটিংস</string> <string name="title_settings">সেটিংস</string>
<string name="title_advanced">এডভান্সড সেটিংস</string> <string name="title_advanced">এডভান্সড সেটিংস</string>
<string name="title_core_settings">কোর সেটিংস</string>
<string name="title_vpn_settings">VPN সেটিংস</string> <string name="title_vpn_settings">VPN সেটিংস</string>
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string> <string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string> <string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
@@ -181,6 +183,9 @@
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string> <string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string> <string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +243,16 @@
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string> <string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string>
<string name="title_core_loglevel">লগ স্তর</string> <string name="title_core_loglevel">লগ স্তর</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">মোড</string> <string name="title_mode">মোড</string>
<string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string> <string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
<string name="title_language">ভাষা</string> <string name="title_language">ভাষা</string>
<string name="title_ui_settings">ইউআই সেটিংস</string> <string name="title_ui_settings">ইউআই সেটিংস</string>
<string name="title_pref_ui_mode_night">ইউআই মোড সেটিংস</string> <string name="title_pref_ui_mode_night">ইউআই মোড সেটিংস</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun Read/Write Timeout (ms, default 300000)</string>
<string name="title_logcat">লগক্যাট</string> <string name="title_logcat">লগক্যাট</string>
<string name="logcat_copy">কপি করুন</string> <string name="logcat_copy">কপি করুন</string>
@@ -265,6 +275,7 @@
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string> <string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string> <string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string> <string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string> <string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string> <string name="title_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string>
<string name="title_filter_config">কনফিগারেশন ফাইল ফিল্টার করুন</string> <string name="title_filter_config">কনফিগারেশন ফাইল ফিল্টার করুন</string>
@@ -315,6 +326,7 @@
<string name="update_new_version_found">New version found: %s</string> <string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string> <string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string> <string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QR কোড</item> <item>QR কোড</item>
@@ -358,4 +370,18 @@
<item>Not Bypass</item> <item>Not Bypass</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string> <string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string>
<string name="server_lab_short_id">ShortID</string> <string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">کیلیت سیخومی</string> <string name="server_lab_secret_key">کیلیت سیخومی</string>
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string> <string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string> <string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
@@ -140,6 +141,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">سامووا</string> <string name="title_settings">سامووا</string>
<string name="title_advanced">سامووا پؽش رئڌه</string> <string name="title_advanced">سامووا پؽش رئڌه</string>
<string name="title_core_settings">سامووا هسته</string>
<string name="title_vpn_settings">سامووا VPN</string> <string name="title_vpn_settings">سامووا VPN</string>
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string> <string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string> <string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string>
@@ -148,7 +150,7 @@
<string name="title_mux_settings">سامووا Mux</string> <string name="title_mux_settings">سامووا Mux</string>
<string name="title_pref_mux_enabled">ر وندن Mux</string> <string name="title_pref_mux_enabled">ر وندن Mux</string>
<string name="summary_pref_mux_enabled">زل تر، ٱما گاشڌ منپیز زی قت بۊ بارت دؽوۉداری، TCP، UDP و QUIC ن ای لم سفارشی کۊنین.</string> <string name="summary_pref_mux_enabled">زل تر، ٱما گاشڌ منپیز زی قت بۊ\nمخزن ترافیک TCP وا 8 منپیز پؽش فرز، بارت دؽوۉداری UDP وو QUIC ن ای لم سفارشی کۊنین.</string>
<string name="title_pref_mux_concurency">منپیزا TCP (تلایه منجا 1-1024)</string> <string name="title_pref_mux_concurency">منپیزا TCP (تلایه منجا 1-1024)</string>
<string name="title_pref_mux_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string> <string name="title_pref_mux_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string>
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string> <string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string>
@@ -158,16 +160,16 @@
<item>گوم زیڌن</item> <item>گوم زیڌن</item>
</string-array> </string-array>
<string name="title_pref_speed_enabled">ر وندن نشۉݩ داڌن سورعت</string> <string name="title_pref_speed_enabled">ر وندن نشووݩ داڌن سورعت</string>
<string name="summary_pref_speed_enabled">نشۉݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</string> <string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</string>
<string name="title_pref_sniffing_enabled">ر وندن Sniffing</string> <string name="title_pref_sniffing_enabled">ر وندن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff ن ز کتن امتهۉݩ کۊنین (پؽش فرز رۊشن)</string> <string name="summary_pref_sniffing_enabled">دامنه sniff ن ز کتن امتهووݩ کۊنین (پؽش فرز رۊشن)</string>
<string name="title_pref_route_only_enabled">ر وندن routeOnly</string> <string name="title_pref_route_only_enabled">ر وندن routeOnly</string>
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string> <string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی موورد نزرن و عونوان نشۊوی IP ووردارین.</string>
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string> <string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
<string name="summary_pref_local_dns_enabled">DNS پردازشت وابیڌه و دس هسته ماژول DNS (پؽشنهاڌ ابۊ، ٱر نیاز هڌ ک جوستن تور وو ولات ٱسلین دور زنی)</string> <string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین فعال بۊ)</string>
<string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string> <string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string>
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string> <string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
@@ -181,6 +183,9 @@
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string> <string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
<string name="title_pref_vpn_interface_address">نشۊوی رابت VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string> <string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -204,21 +209,21 @@
<string name="summary_pref_local_dns_port">پورت DNS مهلی</string> <string name="summary_pref_local_dns_port">پورت DNS مهلی</string>
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string> <string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ</string> <string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ.</string>
<string name="title_pref_start_scan_immediate">زی اسکنن ر ون</string> <string name="title_pref_start_scan_immediate">زی اسکنن ر ون</string>
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، اندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string> <string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، ٱندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string> <string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string> <string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string> <string name="title_pref_double_column_display">ر وندن نشووݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن وا برنومه ن ز نۊ ر ونین.</string> <string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
<!-- AboutActivity --> <!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string> <string name="title_pref_feedback">فشناڌن منشڌ</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسوو موشکلا من Github</string> <string name="summary_pref_feedback">فشناڌن منشڌ یا داسووݩ موشکلا من Github</string>
<string name="summary_pref_tg_group">ٱووڌن من جرگه تلگرام</string> <string name="summary_pref_tg_group">ٱووڌن من بونکۊ تلگرام</string>
<string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string> <string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string>
<string name="title_privacy_policy">هریم سیخومی</string> <string name="title_privacy_policy">هریم سیخومی</string>
<string name="title_about">زبار</string> <string name="title_about">زبار</string>
@@ -238,21 +243,26 @@
<string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string> <string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string>
<string name="title_core_loglevel">سئت داسووا</string> <string name="title_core_loglevel">سئت داسووا</string>
<string name="title_outbound_domain_resolve_method">بارت پؽش هل دامنه دری</string>
<string name="title_mode">هالت</string> <string name="title_mode">هالت</string>
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string> <string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
<string name="title_language">زۉݩ</string> <string name="title_language">زووݩ</string>
<string name="title_ui_settings">سامووا رابت منتوری</string> <string name="title_ui_settings">سامووا رابت منتوری</string>
<string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string> <string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string>
<string name="title_pref_use_hev_tunnel">فعال کردن ویژیی نۊ TUN</string>
<string name="summary_pref_use_hev_tunnel">ٱر ک فعال بۊ، TUN ایا hev-socks5-tunnel ن و کار اگره؛ ٱندی ز badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun سئت گوزارشا</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun زمووݩ مندیر بیڌن خوندن وو هؽل کردن (میلی سانیه، پؽش فرز 300000)</string>
<string name="title_logcat">داسووا</string> <string name="title_logcat">داسووا</string>
<string name="logcat_copy">لف گیری</string> <string name="logcat_copy">لف گیری</string>
<string name="logcat_clear">روفتن</string> <string name="logcat_clear">روفتن</string>
<string name="title_service_restart">ر وندن دووارته خدمات</string> <string name="title_service_restart">ر وندن دووارته خدمات</string>
<string name="title_del_all_config">پاک کردن پوی کانفیگا جرگه سکویی</string> <string name="title_del_all_config">پاک کردن پوی کانفیگا بونکۊ سکویی</string>
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری جرگه سکویی</string> <string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری بونکۊ سکویی</string>
<string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر جرگه سکویی</string> <string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر بونکۊ سکویی</string>
<string name="title_export_all">و در کشیڌن کانفیگا غیر سفارشی جرگه سکویی من کلیپ بورد</string> <string name="title_export_all">و در کشیڌن کانفیگا غیر سفارشی بونکۊ سکویی من کلیپ بورد</string>
<string name="title_sub_setting">سامووا جرگه اشتراک</string> <string name="title_sub_setting">سامووا بونکۊ اشتراک</string>
<string name="sub_setting_remarks">نیشتنا</string> <string name="sub_setting_remarks">نیشتنا</string>
<string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string> <string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string>
<string name="sub_setting_filter">نوم موستعار فیلتر</string> <string name="sub_setting_filter">نوم موستعار فیلتر</string>
@@ -262,13 +272,14 @@
<string name="sub_setting_pre_profile">نوم موستعار پروکسی دیندایی</string> <string name="sub_setting_pre_profile">نوم موستعار پروکسی دیندایی</string>
<string name="sub_setting_next_profile">نوم موستعار پروکسی نیایی</string> <string name="sub_setting_next_profile">نوم موستعار پروکسی نیایی</string>
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string> <string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string> <string name="title_sub_update">ورۊ کردن اشتراک بونکۊ سکویی</string>
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string> <string name="title_ping_all_server">Tcping کانفیگا بونکۊ سکویی</string>
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string> <string name="title_real_ping_all_server">تئخیر واقعی کانفیگا بونکۊ سکویی</string>
<string name="title_create_intelligent_selection_all_server">وورکل پسند هۊشمند کانفیگ بونکۊ سکویی</string>
<string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string> <string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string>
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string> <string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگا</string> <string name="title_filter_config">فیلتر کردن کانفیگا</string>
<string name="filter_config_all">پوی جرگه یل کانفیگ</string> <string name="filter_config_all">پوی بونکۊ یل کانفیگ</string>
<string name="title_del_duplicate_config_count">پاک کردن %d کانفیگ تکراری</string> <string name="title_del_duplicate_config_count">پاک کردن %d کانفیگ تکراری</string>
<string name="title_del_config_count">پاک کردن %d کانفیگ</string> <string name="title_del_config_count">پاک کردن %d کانفیگ</string>
@@ -324,6 +335,7 @@
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string> <string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string> <string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
<string name="update_check_pre_release">واجۊری نوسخه یل پؽش ز تیجنیڌن</string> <string name="update_check_pre_release">واجۊری نوسخه یل پؽش ز تیجنیڌن</string>
<string name="update_checking_for_update">ورۊ رسۊوی ن هونی واجۊری اکونه...</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QRcode</item> <item>QRcode</item>
@@ -368,4 +380,18 @@
<item>دور زیڌه نبۊ</item> <item>دور زیڌه نبۊ</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>هل وو فسل مکۊنین</item>
<item>هل وو ٱووردن و میزبووݩ یل دامنه DNS</item>
<item>هل وو جایونی دامنه</item>
</string-array>
<string name="intelligent_selection">پسند هۊشمند</string>
<string name="sub_setting_intelligent_selection_filter">گوڌنا دیاری پسند هۊشمند فیلتر مئمۊلی</string>
<string name="title_intelligent_selection_method">بارت پسند هۊشمند</string>
<string-array name="intelligent_selection_method">
<item>کم ترین پینگ</item>
<item>کم ترین بار(لود)</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string> <string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
<string name="server_lab_short_id">ShortID</string> <string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">کلید خصوصی</string> <string name="server_lab_secret_key">کلید خصوصی</string>
<string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string> <string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
<string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string> <string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string>
@@ -137,6 +138,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">تنظیمات</string> <string name="title_settings">تنظیمات</string>
<string name="title_advanced">تنظیمات پیشرفته</string> <string name="title_advanced">تنظیمات پیشرفته</string>
<string name="title_core_settings">تنظیمات هسته</string>
<string name="title_vpn_settings">تنظیمات VPN</string> <string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string> <string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string> <string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string>
@@ -179,6 +181,9 @@
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string> <string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
<string name="title_pref_vpn_interface_address">آدرس واسط VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string> <string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -235,11 +240,16 @@
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string> <string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
<string name="title_pref_auto_update_interval">فاصله به‌ روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string> <string name="title_pref_auto_update_interval">فاصله به‌ روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
<string name="title_core_loglevel">سطح گزارشات</string> <string name="title_core_loglevel">سطح گزارشات</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">حالت</string> <string name="title_mode">حالت</string>
<string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string> <string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
<string name="title_language">زبان</string> <string name="title_language">زبان</string>
<string name="title_ui_settings">تنظیمات رابط کاربری</string> <string name="title_ui_settings">تنظیمات رابط کاربری</string>
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string> <string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
<string name="title_pref_use_hev_tunnel">فعالسازی قابلیت TUN جدید</string>
<string name="summary_pref_use_hev_tunnel">در صورت فعال بودن، TUN از hev-socks5-tunnel استفاده می‌کند؛ در غیر این صورت از badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">سطح گزارشات Hev Tun</string>
<string name="title_pref_hev_tunnel_rw_timeout">زمان انتظار خواندن/نوشتن (میلی‌ثانیه، پیش‌فرض ۳۰۰۰۰۰) Hev Tun</string>
<string name="title_logcat">گزارشات</string> <string name="title_logcat">گزارشات</string>
<string name="logcat_copy">کپی</string> <string name="logcat_copy">کپی</string>
@@ -262,6 +272,7 @@
<string name="title_sub_update">به‌روزرسانی گروه فعلی اشتراک</string> <string name="title_sub_update">به‌روزرسانی گروه فعلی اشتراک</string>
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string> <string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string> <string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
<string name="title_create_intelligent_selection_all_server">ایجاد کانفیگ انتخاب هوشمند برای گروه فعلی</string>
<string name="title_user_asset_setting">فایل های منبع جغرافیایی</string> <string name="title_user_asset_setting">فایل های منبع جغرافیایی</string>
<string name="title_sort_by_test_results">مرتب‌ سازی بر اساس نتایج آزمایش</string> <string name="title_sort_by_test_results">مرتب‌ سازی بر اساس نتایج آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگ‌ها</string> <string name="title_filter_config">فیلتر کردن کانفیگ‌ها</string>
@@ -321,6 +332,7 @@
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string> <string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
<string name="update_now">اکنون به روز رسانی کنید</string> <string name="update_now">اکنون به روز رسانی کنید</string>
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string> <string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
<string name="update_checking_for_update">در حال بررسی برای به‌روزرسانی…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QRcode</item> <item>QRcode</item>
@@ -367,4 +379,18 @@
<item>دور زده نشود</item> <item>دور زده نشود</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">انتخاب هوشمند</string>
<string name="sub_setting_intelligent_selection_filter">فیلتر نام مستعار برای انتخاب هوشمند</string>
<string name="title_intelligent_selection_method">روش انتخاب هوشمند</string>
<string-array name="intelligent_selection_method">
<item>کمترین پینگ</item>
<item>کمترین بار(لود)</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">Дополнительный ключ шифрования (необязательно)</string> <string name="server_lab_preshared_key">Дополнительный ключ шифрования (необязательно)</string>
<string name="server_lab_short_id">ShortID</string> <string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">mldsa65Verify</string>
<string name="server_lab_secret_key">Закрытый ключ</string> <string name="server_lab_secret_key">Закрытый ключ</string>
<string name="server_lab_reserved">Reserved (необязательно, через запятую)</string> <string name="server_lab_reserved">Reserved (необязательно, через запятую)</string>
<string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string> <string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string>
@@ -139,6 +140,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Настройки</string> <string name="title_settings">Настройки</string>
<string name="title_advanced">Расширенные настройки</string> <string name="title_advanced">Расширенные настройки</string>
<string name="title_core_settings">Настройки ядра</string>
<string name="title_vpn_settings">Настройки VPN</string> <string name="title_vpn_settings">Настройки VPN</string>
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string> <string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string>
<string name="summary_pref_per_app_proxy">Основной: выбранное приложение соединяется через прокси, не выбранное — напрямую;\nРежим обхода: выбранное приложение соединяется напрямую, не выбранное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string> <string name="summary_pref_per_app_proxy">Основной: выбранное приложение соединяется через прокси, не выбранное — напрямую;\nРежим обхода: выбранное приложение соединяется напрямую, не выбранное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string>
@@ -152,9 +154,9 @@
<string name="title_pref_mux_xudp_concurency">XUDP-соединения (диапазон от 1 до 1024)</string> <string name="title_pref_mux_xudp_concurency">XUDP-соединения (диапазон от 1 до 1024)</string>
<string name="title_pref_mux_xudp_quic">Обработка QUIC в мультиплексном туннеле</string> <string name="title_pref_mux_xudp_quic">Обработка QUIC в мультиплексном туннеле</string>
<string-array name="mux_xudp_quic_entries"> <string-array name="mux_xudp_quic_entries">
<item>отклонять</item> <item>Отклонять</item>
<item>разрешать</item> <item>Разрешать</item>
<item>пропускать</item> <item>Пропускать</item>
</string-array> </string-array>
<string name="title_pref_speed_enabled">Отображение скорости</string> <string name="title_pref_speed_enabled">Отображение скорости</string>
@@ -178,7 +180,10 @@
<string name="summary_pref_remote_dns">DNS</string> <string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string> <string name="title_pref_vpn_bypass_lan">VPN обходит LAN</string>
<string name="title_pref_vpn_interface_address">Адрес интерфейса VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (по умолчанию 1500)</string>
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string> <string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -193,7 +198,7 @@
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать локальный прокси. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string> <string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать локальный прокси. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в доверенной сети</string> <string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в доверенной сети</string>
<string name="title_pref_allow_insecure">Разрешать небезопасные</string> <string name="title_pref_allow_insecure">Разрешать небезопасные соединения</string>
<string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string> <string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string>
<string name="title_pref_socks_port">Порт локального прокси</string> <string name="title_pref_socks_port">Порт локального прокси</string>
@@ -237,11 +242,16 @@
<string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string> <string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string>
<string name="title_core_loglevel">Подробность ведения журнала</string> <string name="title_core_loglevel">Подробность ведения журнала</string>
<string name="title_outbound_domain_resolve_method">Предопределение исходящего домена</string>
<string name="title_mode">Режим</string> <string name="title_mode">Режим</string>
<string name="title_mode_help">Нажмите для получения дополнительной информации</string> <string name="title_mode_help">Нажмите для получения дополнительной информации</string>
<string name="title_language">Язык</string> <string name="title_language">Язык</string>
<string name="title_ui_settings">Настройки интерфейса</string> <string name="title_ui_settings">Настройки интерфейса</string>
<string name="title_pref_ui_mode_night">Тема интерфейса</string> <string name="title_pref_ui_mode_night">Тема интерфейса</string>
<string name="title_pref_use_hev_tunnel">Использовать новую версию TUN</string>
<string name="summary_pref_use_hev_tunnel">Если включено, TUN будет использовать hev-socks5-tunnel, иначе будет использоваться badvpn-tun2socks</string>
<string name="title_pref_hev_tunnel_loglevel">Подробность журнала HevTun</string>
<string name="title_pref_hev_tunnel_rw_timeout">Время ожидания чтения/записи HevTun (мс, по умолчанию 300000)</string>
<string name="title_logcat">Журнал</string> <string name="title_logcat">Журнал</string>
<string name="logcat_copy">Копировать</string> <string name="logcat_copy">Копировать</string>
@@ -262,10 +272,11 @@
<string name="sub_setting_next_profile">Следующая конфигурация прокси</string> <string name="sub_setting_next_profile">Следующая конфигурация прокси</string>
<string name="sub_setting_pre_profile_tip">Конфигурация должна быть уникальной</string> <string name="sub_setting_pre_profile_tip">Конфигурация должна быть уникальной</string>
<string name="title_sub_update">Обновить подписку группы</string> <string name="title_sub_update">Обновить подписку группы</string>
<string name="title_ping_all_server">Проверка профилей группы</string> <string name="title_ping_all_server">Проверить профили группы</string>
<string name="title_real_ping_all_server">Время отклика профилей группы</string> <string name="title_real_ping_all_server">Время отклика профилей группы</string>
<string name="title_create_intelligent_selection_all_server">Создать конфигурацию умного выбора</string>
<string name="title_user_asset_setting">Файлы ресурсов</string> <string name="title_user_asset_setting">Файлы ресурсов</string>
<string name="title_sort_by_test_results">Сортировка по результатам теста</string> <string name="title_sort_by_test_results">Сортировать по результатам теста</string>
<string name="title_filter_config">Фильтр групп</string> <string name="title_filter_config">Фильтр групп</string>
<string name="filter_config_all">Все группы</string> <string name="filter_config_all">Все группы</string>
<string name="title_del_duplicate_config_count">Удалено дубликатов профилей: %d</string> <string name="title_del_duplicate_config_count">Удалено дубликатов профилей: %d</string>
@@ -323,6 +334,7 @@
<string name="update_new_version_found">Найдена новая версия: %s</string> <string name="update_new_version_found">Найдена новая версия: %s</string>
<string name="update_now">Обновить</string> <string name="update_now">Обновить</string>
<string name="update_check_pre_release">Искать предварительный выпуск</string> <string name="update_check_pre_release">Искать предварительный выпуск</string>
<string name="update_checking_for_update">Проверка обновления…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QR-код</item> <item>QR-код</item>
@@ -363,8 +375,22 @@
<string-array name="vpn_bypass_lan"> <string-array name="vpn_bypass_lan">
<item>Как в профиле</item> <item>Как в профиле</item>
<item>Пропускает</item> <item>Обходит</item>
<item>Не пропускает</item> <item>Не обходит</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Не определять</item>
<item>Определять и добавлять к узлам DNS</item>
<item>Определять и заменять домен</item>
</string-array>
<string name="intelligent_selection">Умный выбор</string>
<string name="sub_setting_intelligent_selection_filter">Название фильтра умного выбора</string>
<string name="title_intelligent_selection_method">Принцип умного выбора</string>
<string-array name="intelligent_selection_method">
<item>Наименьшая задержка</item>
<item>Наименьшая нагрузка</item>
</string-array>
<string name="pre_resolving_domain">Предварительное определение домена…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string> <string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</string> <string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string> <string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (Không bắt buộc)</string> <string name="server_lab_reserved">Reserved (Không bắt buộc)</string>
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string> <string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string>
@@ -138,6 +139,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Cài đặt</string> <string name="title_settings">Cài đặt</string>
<string name="title_advanced">Cài đặt nâng cao</string> <string name="title_advanced">Cài đặt nâng cao</string>
<string name="title_core_settings">Cài đặt lõi</string>
<string name="title_vpn_settings">Cài đặt VPN</string> <string name="title_vpn_settings">Cài đặt VPN</string>
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string> <string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string> <string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
@@ -181,6 +183,9 @@
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string> <string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string> <string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string> <string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +243,16 @@
<string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string> <string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string>
<string name="title_core_loglevel">Cấp độ nhật ký</string> <string name="title_core_loglevel">Cấp độ nhật ký</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Chế độ kết nối</string> <string name="title_mode">Chế độ kết nối</string>
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string> <string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
<string name="title_language">Ngôn ngữ</string> <string name="title_language">Ngôn ngữ</string>
<string name="title_ui_settings">Cài đặt UI</string> <string name="title_ui_settings">Cài đặt UI</string>
<string name="title_pref_ui_mode_night">Cài đặt chế độ UI</string> <string name="title_pref_ui_mode_night">Cài đặt chế độ UI</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun Read/Write Timeout (ms, default 300000)</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">Sao chép</string> <string name="logcat_copy">Sao chép</string>
@@ -265,6 +275,7 @@
<string name="title_sub_update">Cập nhật các gói đăng ký</string> <string name="title_sub_update">Cập nhật các gói đăng ký</string>
<string name="title_ping_all_server">Ping tất cả máy chủ</string> <string name="title_ping_all_server">Ping tất cả máy chủ</string>
<string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string> <string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string> <string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string> <string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string> <string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
@@ -317,6 +328,7 @@
<string name="update_new_version_found">New version found: %s</string> <string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string> <string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string> <string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item> <item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
@@ -355,4 +367,18 @@
<item>Not Bypass</item> <item>Not Bypass</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string> <string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</string> <string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string> <string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (可选,逗号隔开)</string> <string name="server_lab_reserved">Reserved (可选,逗号隔开)</string>
<string name="server_lab_local_address">本地地址 (可选 IPv4/IPv6逗号隔开)</string> <string name="server_lab_local_address">本地地址 (可选 IPv4/IPv6逗号隔开)</string>
@@ -137,6 +138,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">设置</string> <string name="title_settings">设置</string>
<string name="title_advanced">进阶设置</string> <string name="title_advanced">进阶设置</string>
<string name="title_core_settings">核心设置</string>
<string name="title_vpn_settings">VPN 设置</string> <string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用</string> <string name="title_pref_per_app_proxy">分应用</string>
<string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string> <string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
@@ -178,6 +180,9 @@
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string> <string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string>
<string name="title_pref_vpn_interface_address">VPN 接口地址</string>
<string name="title_pref_vpn_mtu">VPN MTU (默认 1500)</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string> <string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -235,11 +240,16 @@
<string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15</string> <string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15</string>
<string name="title_core_loglevel">日志级别</string> <string name="title_core_loglevel">日志级别</string>
<string name="title_outbound_domain_resolve_method">Outbound 域名预解析方式</string>
<string name="title_mode">模式</string> <string name="title_mode">模式</string>
<string name="title_mode_help">点此查看更多帮助</string> <string name="title_mode_help">点此查看更多帮助</string>
<string name="title_language">语言</string> <string name="title_language">语言</string>
<string name="title_ui_settings">用户界面设置</string> <string name="title_ui_settings">用户界面设置</string>
<string name="title_pref_ui_mode_night">界面颜色设置</string> <string name="title_pref_ui_mode_night">界面颜色设置</string>
<string name="title_pref_use_hev_tunnel">启用新的 TUN 功能</string>
<string name="summary_pref_use_hev_tunnel">选择启用后 TUN 将使用 hev-socks5-tunnel 否则使用 badvpn-tun2socks</string>
<string name="title_pref_hev_tunnel_loglevel">HevTun 日志级别</string>
<string name="title_pref_hev_tunnel_rw_timeout">HevTun 读写超时 (ms, 默认 300000)</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</string> <string name="logcat_copy">复制</string>
@@ -262,6 +272,7 @@
<string name="title_sub_update">更新当前组订阅</string> <string name="title_sub_update">更新当前组订阅</string>
<string name="title_ping_all_server">测试当前组配置 Tcping</string> <string name="title_ping_all_server">测试当前组配置 Tcping</string>
<string name="title_real_ping_all_server">测试当前组配置真连接</string> <string name="title_real_ping_all_server">测试当前组配置真连接</string>
<string name="title_create_intelligent_selection_all_server">生成当前组智能选择配置</string>
<string name="title_user_asset_setting">资源文件</string> <string name="title_user_asset_setting">资源文件</string>
<string name="title_sort_by_test_results">按测试结果排序</string> <string name="title_sort_by_test_results">按测试结果排序</string>
<string name="title_filter_config">过滤配置文件</string> <string name="title_filter_config">过滤配置文件</string>
@@ -315,6 +326,7 @@
<string name="update_new_version_found">发现新版本: %s</string> <string name="update_new_version_found">发现新版本: %s</string>
<string name="update_now">立即更新</string> <string name="update_now">立即更新</string>
<string name="update_check_pre_release">检查 Pre-release</string> <string name="update_check_pre_release">检查 Pre-release</string>
<string name="update_checking_for_update">正在检查更新中…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>二维码</item> <item>二维码</item>
@@ -359,4 +371,18 @@
<item>不绕过</item> <item>不绕过</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>不解析</item>
<item>解析后添加至 DNS Hosts</item>
<item>解析后替换原域名</item>
</string-array>
<string name="intelligent_selection">智能选择</string>
<string name="sub_setting_intelligent_selection_filter">别名智能选择正则过滤</string>
<string name="title_intelligent_selection_method">智能选择方式</string>
<string-array name="intelligent_selection_method">
<item>最低延迟</item>
<item>最稳定</item>
</string-array>
<string name="pre_resolving_domain">预解析域名中…</string>
</resources> </resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string> <string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</string> <string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string> <string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (可選,逗號隔開)</string> <string name="server_lab_reserved">Reserved (可選,逗號隔開)</string>
<string name="server_lab_local_address">本機位址 (可選 IPv4/IPv6逗號隔開)</string> <string name="server_lab_local_address">本機位址 (可選 IPv4/IPv6逗號隔開)</string>
@@ -138,6 +139,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">設定</string> <string name="title_settings">設定</string>
<string name="title_advanced">進階</string> <string name="title_advanced">進階</string>
<string name="title_core_settings">核心設定</string>
<string name="title_vpn_settings">VPN 設定</string> <string name="title_vpn_settings">VPN 設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string> <string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy未勾選的直接連線\n繞行模式勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string> <string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy未勾選的直接連線\n繞行模式勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
@@ -180,6 +182,9 @@
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string> <string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string>
<string name="title_pref_vpn_interface_address">VPN 介面位址</string>
<string name="title_pref_vpn_mtu">VPN MTU (預設 1500)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string> <string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string> <string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
@@ -236,11 +241,16 @@
<string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15</string> <string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15</string>
<string name="title_core_loglevel">記錄層級</string> <string name="title_core_loglevel">記錄層級</string>
<string name="title_outbound_domain_resolve_method">Outbound 網域預解析方式</string>
<string name="title_mode">模式</string> <string name="title_mode">模式</string>
<string name="title_mode_help">輕觸以檢視說明</string> <string name="title_mode_help">輕觸以檢視說明</string>
<string name="title_language">語言</string> <string name="title_language">語言</string>
<string name="title_ui_settings">介面顏色設定</string> <string name="title_ui_settings">介面顏色設定</string>
<string name="title_pref_ui_mode_night">介面顯示模式</string> <string name="title_pref_ui_mode_night">介面顯示模式</string>
<string name="title_pref_use_hev_tunnel">啟用新 TUN 功能</string>
<string name="summary_pref_use_hev_tunnel">選擇啟用後TUN 將使用 hev-socks5-tunnel否則使用 badvpn-tun2socks。</string>
<string name="title_pref_hev_tunnel_loglevel">HevTun 日誌級別</string>
<string name="title_pref_hev_tunnel_rw_timeout">HevTun 讀寫逾時 (ms, 預設 300000)</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</string> <string name="logcat_copy">複製</string>
@@ -263,6 +273,7 @@
<string name="title_sub_update">更新目前群組訂閱</string> <string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組設定 Tcping</string> <string name="title_ping_all_server">偵測目前群組設定 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組設定真延遲</string> <string name="title_real_ping_all_server">偵測目前群組設定真延遲</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">資源檔案</string> <string name="title_user_asset_setting">資源檔案</string>
<string name="title_sort_by_test_results">依偵測結果排序</string> <string name="title_sort_by_test_results">依偵測結果排序</string>
<string name="title_filter_config">過濾設定</string> <string name="title_filter_config">過濾設定</string>
@@ -315,6 +326,7 @@
<string name="update_new_version_found">發現新版本: %s</string> <string name="update_new_version_found">發現新版本: %s</string>
<string name="update_now">立即更新</string> <string name="update_now">立即更新</string>
<string name="update_check_pre_release">檢查 Pre-release</string> <string name="update_check_pre_release">檢查 Pre-release</string>
<string name="update_checking_for_update">正在檢查更新中…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QR Code</item> <item>QR Code</item>
@@ -359,4 +371,18 @@
<item>不繞過</item> <item>不繞過</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>不解析</item>
<item>解析後加入 DNS Hosts</item>
<item>解析後替換原網域名稱</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -118,6 +118,13 @@
<item>Proxy only</item> <item>Proxy only</item>
</string-array> </string-array>
<string-array name="hev_tunnel_loglevel" translatable="false">
<item>none</item>
<item>error</item>
<item>warn</item>
<item>debug</item>
</string-array>
<string-array name="flows" translatable="false"> <string-array name="flows" translatable="false">
<item></item> <item></item>
<item>xtls-rprx-vision</item> <item>xtls-rprx-vision</item>
@@ -182,4 +189,35 @@
<item>2</item> <item>2</item>
</string-array> </string-array>
<string-array name="vpn_interface_address_value" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</string-array>
<string-array name="vpn_interface_address">
<item>10.10.14.x</item>
<item>10.1.0.x</item>
<item>10.0.0.x</item>
<item>172.31.0.x</item>
<item>172.20.0.x</item>
<item>172.16.0.x</item>
<item>192.168.100.x</item>
</string-array>
<string-array name="outbound_domain_resolve_method_value" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="intelligent_selection_method_value" translatable="false">
<item>0</item>
<item>1</item>
</string-array>
</resources> </resources>

View File

@@ -81,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string> <string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</string> <string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string> <string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string> <string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved(Optional, separated by commas)</string> <string name="server_lab_reserved">Reserved(Optional, separated by commas)</string>
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string> <string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
@@ -140,6 +141,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
<string name="title_advanced">Advanced Settings</string> <string name="title_advanced">Advanced Settings</string>
<string name="title_core_settings">Core Settings</string>
<string name="title_vpn_settings">VPN Settings</string> <string name="title_vpn_settings">VPN Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string> <string name="title_pref_per_app_proxy">Per-app proxy</string>
<string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string> <string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string>
@@ -182,6 +184,11 @@
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string> <string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string> <string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
<string name="title_pref_vpn_mtu">VPN MTU (default 1500)</string>
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string> <string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
@@ -239,11 +246,16 @@
<string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string> <string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string>
<string name="title_core_loglevel">Log Level</string> <string name="title_core_loglevel">Log Level</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Mode</string> <string name="title_mode">Mode</string>
<string name="title_mode_help">Click me for more help</string> <string name="title_mode_help">Click me for more help</string>
<string name="title_language">Language</string> <string name="title_language">Language</string>
<string name="title_ui_settings">UI settings</string> <string name="title_ui_settings">UI settings</string>
<string name="title_pref_ui_mode_night">UI mode settings</string> <string name="title_pref_ui_mode_night">UI mode settings</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun Read/Write Timeout (ms, default 300000)</string>
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">Copy</string> <string name="logcat_copy">Copy</string>
@@ -266,6 +278,7 @@
<string name="title_sub_update">Update current group subscription</string> <string name="title_sub_update">Update current group subscription</string>
<string name="title_ping_all_server">Tcping current group configuration</string> <string name="title_ping_all_server">Tcping current group configuration</string>
<string name="title_real_ping_all_server">Real delay current group configuration</string> <string name="title_real_ping_all_server">Real delay current group configuration</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string> <string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">Sorting by test results</string> <string name="title_sort_by_test_results">Sorting by test results</string>
<string name="title_filter_config">Filter configuration file</string> <string name="title_filter_config">Filter configuration file</string>
@@ -325,6 +338,7 @@
<string name="update_new_version_found">New version found: %s</string> <string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</string> <string name="update_now">Update now</string>
<string name="update_check_pre_release">Check Pre-release</string> <string name="update_check_pre_release">Check Pre-release</string>
<string name="update_checking_for_update">Checking for update…</string>
<string-array name="share_method"> <string-array name="share_method">
<item>QRcode</item> <item>QRcode</item>
@@ -369,4 +383,18 @@
<item>Not Bypass</item> <item>Not Bypass</item>
</string-array> </string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
<string name="pre_resolving_domain">Pre-resolving domain…</string>
</resources> </resources>

View File

@@ -13,6 +13,7 @@
android:key="pref_route_only_enabled" android:key="pref_route_only_enabled"
android:summary="@string/summary_pref_route_only_enabled" android:summary="@string/summary_pref_route_only_enabled"
android:title="@string/title_pref_route_only_enabled" /> android:title="@string/title_pref_route_only_enabled" />
<CheckBoxPreference <CheckBoxPreference
android:key="pref_is_booted" android:key="pref_is_booted"
android:summary="@string/summary_pref_is_booted" android:summary="@string/summary_pref_is_booted"
@@ -20,9 +21,9 @@
<PreferenceCategory android:title="@string/title_vpn_settings"> <PreferenceCategory android:title="@string/title_vpn_settings">
<CheckBoxPreference <CheckBoxPreference
android:key="pref_prefer_ipv6" android:key="pref_prefer_ipv6"
android:summary="@string/summary_pref_prefer_ipv6" android:summary="@string/summary_pref_prefer_ipv6"
android:title="@string/title_pref_prefer_ipv6" /> android:title="@string/title_pref_prefer_ipv6" />
<CheckBoxPreference <CheckBoxPreference
android:key="pref_per_app_proxy" android:key="pref_per_app_proxy"
@@ -56,12 +57,45 @@
android:title="@string/title_pref_vpn_dns" /> android:title="@string/title_pref_vpn_dns" />
<ListPreference <ListPreference
android:defaultValue="0" android:defaultValue="1"
android:entries="@array/vpn_bypass_lan" android:entries="@array/vpn_bypass_lan"
android:entryValues="@array/vpn_bypass_lan_value" android:entryValues="@array/vpn_bypass_lan_value"
android:key="pref_vpn_bypass_lan" android:key="pref_vpn_bypass_lan"
android:summary="%s" android:summary="%s"
android:title="@string/title_pref_vpn_bypass_lan" /> android:title="@string/title_pref_vpn_bypass_lan" />
<ListPreference
android:defaultValue="0"
android:entries="@array/vpn_interface_address"
android:entryValues="@array/vpn_interface_address_value"
android:key="pref_vpn_interface_address_config_index"
android:summary="%s"
android:title="@string/title_pref_vpn_interface_address" />
<EditTextPreference
android:inputType="number"
android:key="pref_vpn_mtu"
android:summary="1500"
android:title="@string/title_pref_vpn_mtu" />
<CheckBoxPreference
android:key="pref_use_hev_tunnel"
android:summary="@string/summary_pref_use_hev_tunnel"
android:title="@string/title_pref_use_hev_tunnel" />
<ListPreference
android:defaultValue="none"
android:entries="@array/hev_tunnel_loglevel"
android:entryValues="@array/hev_tunnel_loglevel"
android:key="pref_hev_tunnel_loglevel"
android:summary="%s"
android:title="@string/title_pref_hev_tunnel_loglevel" />
<EditTextPreference
android:inputType="number"
android:key="pref_hev_tunnel_rw_timeout"
android:summary="300000"
android:title="@string/title_pref_hev_tunnel_rw_timeout" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/title_ui_settings"> <PreferenceCategory android:title="@string/title_ui_settings">
@@ -171,9 +205,7 @@
android:title="@string/title_pref_auto_update_interval" /> android:title="@string/title_pref_auto_update_interval" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory android:title="@string/title_core_settings">
android:title="@string/title_advanced"
app:initialExpandedChildrenCount="0">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
@@ -208,11 +240,6 @@
android:summary="@string/summary_pref_dns_hosts" android:summary="@string/summary_pref_dns_hosts"
android:title="@string/title_pref_dns_hosts" /> android:title="@string/title_pref_dns_hosts" />
<EditTextPreference
android:key="pref_delay_test_url"
android:summary="@string/summary_pref_delay_test_url"
android:title="@string/title_pref_delay_test_url" />
<ListPreference <ListPreference
android:defaultValue="warning" android:defaultValue="warning"
android:entries="@array/core_loglevel" android:entries="@array/core_loglevel"
@@ -221,6 +248,31 @@
android:summary="%s" android:summary="%s"
android:title="@string/title_core_loglevel" /> android:title="@string/title_core_loglevel" />
<ListPreference
android:defaultValue="1"
android:entries="@array/outbound_domain_resolve_method"
android:entryValues="@array/outbound_domain_resolve_method_value"
android:key="pref_outbound_domain_resolve_method"
android:summary="%s"
android:title="@string/title_outbound_domain_resolve_method" />
<ListPreference
android:defaultValue="0"
android:entries="@array/intelligent_selection_method"
android:entryValues="@array/intelligent_selection_method_value"
android:key="pref_intelligent_selection_method"
android:summary="%s"
android:title="@string/title_intelligent_selection_method" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_advanced">
<EditTextPreference
android:key="pref_delay_test_url"
android:summary="@string/summary_pref_delay_test_url"
android:title="@string/title_pref_delay_test_url" />
<ListPreference <ListPreference
android:defaultValue="VPN" android:defaultValue="VPN"
android:entries="@array/mode_entries" android:entries="@array/mode_entries"
@@ -230,4 +282,5 @@
android:title="@string/title_mode" /> android:title="@string/title_mode" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -10,31 +10,31 @@ class HttpUtilTest {
fun testIdnToASCII() { fun testIdnToASCII() {
// Regular URL remains unchanged // Regular URL remains unchanged
val regularUrl = "https://example.com/path" val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl)) assertEquals(regularUrl, HttpUtil.toIdnUrl(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode) // Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/path" val nonAsciiUrl = "https://例子.测试/path"
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path" val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl)) assertEquals(expectedNonAscii, HttpUtil.toIdnUrl(nonAsciiUrl))
// Mixed URL only converts the host part // Mixed URL only converts the host part
val mixedUrl = "https://例子.com/测试" val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试" val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl)) assertEquals(expectedMixed, HttpUtil.toIdnUrl(mixedUrl))
// URL with Basic Authentication using regular domain // URL with Basic Authentication using regular domain
val basicAuthUrl = "https://user:password@example.com/path" val basicAuthUrl = "https://user:password@example.com/path"
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl)) assertEquals(basicAuthUrl, HttpUtil.toIdnUrl(basicAuthUrl))
// URL with Basic Authentication using non-ASCII domain // URL with Basic Authentication using non-ASCII domain
val basicAuthNonAscii = "https://user:password@例子.测试/path" val basicAuthNonAscii = "https://user:password@例子.测试/path"
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path" val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii)) assertEquals(expectedBasicAuthNonAscii, HttpUtil.toIdnUrl(basicAuthNonAscii))
// URL with non-ASCII username and password // URL with non-ASCII username and password
val nonAsciiAuth = "https://用户:密码@example.com/path" val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately // Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth)) assertEquals(nonAsciiAuth, HttpUtil.toIdnUrl(nonAsciiAuth))
} }

View File

@@ -1,17 +1,17 @@
[versions] [versions]
agp = "8.9.2" agp = "8.12.1"
desugarJdkLibs = "2.1.5" desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8" gradleLicensePlugin = "0.9.8"
kotlin = "2.1.20" kotlin = "2.2.10"
coreKtx = "1.16.0" coreKtx = "1.16.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.3.0"
espressoCore = "3.6.1" espressoCore = "3.7.0"
appcompat = "1.7.0" appcompat = "1.7.1"
material = "1.12.0" material = "1.12.0"
activity = "1.10.1" activity = "1.10.1"
constraintlayout = "2.2.1" constraintlayout = "2.2.1"
mmkvStatic = "1.3.12" mmkvStatic = "1.3.14"
gson = "2.12.1" gson = "2.12.1"
quickieFoss = "1.14.0" quickieFoss = "1.14.0"
kotlinxCoroutinesAndroid = "1.10.2" kotlinxCoroutinesAndroid = "1.10.2"
@@ -20,8 +20,8 @@ swiperefreshlayout = "1.1.0"
toasty = "1.5.2" toasty = "1.5.2"
editorkit = "2.9.0" editorkit = "2.9.0"
core = "3.5.3" core = "3.5.3"
workRuntimeKtx = "2.10.1" workRuntimeKtx = "2.10.3"
lifecycleViewmodelKtx = "2.8.7" lifecycleViewmodelKtx = "2.9.2"
multidex = "2.0.1" multidex = "2.0.1"
mockitoMockitoInline = "5.2.0" mockitoMockitoInline = "5.2.0"
flexbox = "3.0.0" flexbox = "3.0.0"

View File

@@ -1,6 +1,6 @@
#Thu Nov 14 12:42:51 BDT 2024 #Thu Nov 14 12:42:51 BDT 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -30,3 +30,29 @@ $NDK_HOME/ndk-build \
cp -r $TMPDIR/libs $__dir/ cp -r $TMPDIR/libs $__dir/
popd popd
rm -rf $TMPDIR rm -rf $TMPDIR
#build hev-socks5-tunnel
HEVTUN_TMP=$(mktemp -d)
trap 'rm -rf "$HEVTUN_TMP"' EXIT
mkdir -p "$HEVTUN_TMP/jni"
pushd "$HEVTUN_TMP"
echo 'include $(call all-subdir-makefiles)' > jni/Android.mk
ln -s "$__dir/hev-socks5-tunnel" jni/hev-socks5-tunnel
"$NDK_HOME/ndk-build" \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=jni/Android.mk \
"APP_ABI=armeabi-v7a arm64-v8a x86 x86_64" \
APP_PLATFORM=android-21 \
NDK_LIBS_OUT="$HEVTUN_TMP/libs" \
NDK_OUT="$HEVTUN_TMP/obj" \
"APP_CFLAGS=-O3 -DPKGNAME=com/v2ray/ang/service" \
"APP_LDFLAGS=-WI,--build-id=none -WI,--hash-style=gnu" \
cp -r "$HEVTUN_TMP/libs/"* "$__dir/libs/"
popd
rm -rf "$HEVTUN_TMP"

1
hev-socks5-tunnel Submodule

Submodule hev-socks5-tunnel added at 0239bd443f