Compare commits

...

156 Commits

Author SHA1 Message Date
2dust f85c382dbd up 1.10.32 2025-12-25 17:13:13 +08:00
if 0b76ff0409 fix(accessibility): Add content description for QR code dialog / [无障碍] 修复二维码弹窗缺失内容描述的问题 (#5110)
* 在显示 Dialog 之前,从资源数组中获取第一个元素(即“二维码”),并设置为图片的 contentDescription。

* Update V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-25 17:08:59 +08:00
Skh-web6982 e3bbd47c81 Update Persian translate (#5108) 2025-12-25 17:08:42 +08:00
Skh-web6982 0672820718 Update kotlin version to 2.3.0 (#5107)
* Update kotlin version to 2.3.0

* Update kotlin version to 2.3.0
2025-12-25 17:08:33 +08:00
fuilloi e0e58dff45 Update build.gradle.kts (#5087) 2025-12-25 17:07:26 +08:00
2dust cfae051109 Refactor hev tunnel timeout settings
Changed hev tunnel read/write timeout preference to accept separate TCP and UDP values in seconds (default 300,60). Updated related keys, default values, and UI strings across multiple languages to reflect the new format and units.
https://github.com/2dust/v2rayNG/issues/5057
2025-12-24 20:07:34 +08:00
if 60ddf5ba6b [Accessibility] Fix missing labels for buttons and FAB / 修复主界面无障碍标签缺失问题 (#5105)
* 无障碍修复

* Update 3 files:
item_recycler_main.xml, strings.xml, strings.xml

* Update 3 files:
item_recycler_main.xml, strings.xml, strings.xml

* Update 1 file:
dimens.xml

* Update 4 files:
MainActivity.kt, activity_main.xml, strings.xml, strings.xml

* 恢复原始距离

* 修订成

* 补充完整

* Update V2rayNG/app/src/main/res/values/strings.xml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add 'Edit configuration' string to multiple locales

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-24 19:02:15 +08:00
David 861b1746e3 Update strings.xml (#5097)
將沒有翻譯為繁體中文的信息翻譯為了繁體中文。
2025-12-18 20:56:43 +08:00
Liniya 4dec554b42 Fixes RU translation for server-test menu items (#5091) 2025-12-13 09:39:58 +08:00
2dust d7ad91fe8b up 1.10.31 2025-12-09 10:25:05 +08:00
2dust fb51683981 Update AndroidLibXrayLite 2025-12-09 10:16:44 +08:00
2dust e5d14bada9 up 1.10.30 2025-12-07 15:59:33 +08:00
2dust 80fb265960 Update AndroidLibXrayLite 2025-12-07 15:59:25 +08:00
2dust f5493b08ae Update IP API URL in AppConfig
https://github.com/2dust/v2rayNG/issues/5073
2025-12-06 18:51:53 +08:00
Skh-web6982 4f7d6db361 Update kotlin to version 2.2.21 (#5077)
* Update kotlin to version 2.2.21

* Update kotlin to version 2.2.21

* Update gradle-wrapper.properties

* Update libs.versions.toml
2025-12-06 18:48:15 +08:00
2dust 636b20290a Update submodules AndroidLibXrayLite and hev-socks5-tunnel 2025-12-03 09:38:08 +08:00
2dust 7ead97079c up 1.10.29 2025-12-03 09:36:47 +08:00
fuilloi e42cc3a60a fix hevtun loglevel (#5062) 2025-11-27 19:14:22 +08:00
fuilloi d231490917 update libtun build script (#5055)
* Update compile-tun2socks.sh

* update libtun build script
2025-11-26 15:21:32 +08:00
fuilloi 521d61acb3 update hev config option (#5056) 2025-11-26 13:43:44 +08:00
2dust ed9c26b012 Add user agent support for subscription updates
https://github.com/2dust/v2rayNG/issues/5043
2025-11-22 18:01:49 +08:00
2dust e782da7c71 Update hev-socks5-tunnel and hysteria submodules
Advanced the hev-socks5-tunnel and hysteria submodules to their latest commits to incorporate recent upstream changes.
2025-11-19 16:59:34 +08:00
2dust 6fd0f7f0db Update .gitmodules 2025-11-19 16:54:44 +08:00
2dust 7c14a0c200 Bug fix 2025-11-17 17:46:15 +08:00
2dust 2789db2c70 up 1.10.28 2025-11-16 11:43:54 +08:00
2dust 8aee96b5eb Update Google proxy routing rules 2025-11-16 11:37:47 +08:00
solokot 02d58dde9a Update Russian translation (#5031)
for https://github.com/2dust/v2rayNG/commit/b961c86066064c369c876c9850f65c3bf40e65a2
2025-11-12 19:10:32 +08:00
2dust 623f5446f2 Add portHoppingInterval support to Hysteria2Fmt
https://github.com/2dust/v2rayNG/issues/5009
2025-11-10 21:06:18 +08:00
2dust b961c86066 Fix
https://github.com/2dust/v2rayNG/issues/5009
2025-11-10 20:51:04 +08:00
Hossein Abaspanah 3e4f0e9b5b Update Luri Bakhtiari translation (#5024) 2025-11-10 19:56:58 +08:00
2dust c0141225d3 Add allowInsecure and insecure to the shared URI
https://github.com/2dust/v2rayN/issues/8267
2025-11-09 17:33:40 +08:00
2dust 026bf6a37b up 1.10.27 2025-11-06 18:21:43 +08:00
solokot c117a334ce Russian translation improvements (#4997) 2025-11-02 10:15:59 +08:00
2dust 051520de37 Set HEV tunnel preference default to true
Changed the default value of the HEV tunnel setting to true across the app, including preference XML, settings UI, config manager, and view model. This ensures that the HEV tunnel is enabled by default for new users and maintains consistent default behavior throughout the application.
2025-11-01 11:29:08 +08:00
2dust b5146e4712 Comment out per-app proxy setting in preferences 2025-11-01 11:12:15 +08:00
2dust 4457b6b2c9 Refactor PerAppProxyActivity and add auto-enable logic 2025-11-01 11:08:41 +08:00
2dust 12edb051c3 Update config-related strings for clarity and consistency 2025-11-01 10:13:03 +08:00
2dust 923e3f32d1 up 1.10.26 2025-10-26 17:46:30 +08:00
2dust 5490db90c0 up 1.10.25 2025-10-26 17:39:37 +08:00
2dust cb68d42291 Check for update to add fdroid 2025-10-26 17:38:51 +08:00
2dust ec3a8e80d6 up 1.10.25 2025-10-26 16:30:28 +08:00
2dust eb0960125b Update build.yml 2025-10-26 16:29:40 +08:00
2dust 12b70e2088 Adjust the adapter that can modify APPLICATION_ID
Add APPLICATION_ID to the About page
2025-10-26 16:08:44 +08:00
2dust 0c473dea19 up 1.10.24 2025-10-15 17:57:41 +08:00
2dust e71dd7342a Update AndroidLibXrayLite 2025-10-15 17:56:33 +08:00
2dust a765215dda Update libs.versions.toml 2025-10-10 19:14:46 +08:00
2dust edea3027f7 Fix
https://github.com/2dust/v2rayN/issues/8060
2025-10-03 14:28:31 +08:00
2dust 305219c3bc Update libs.versions.toml 2025-10-03 14:28:24 +08:00
DHR60 9c4188fba7 Adjust DNS routing (#4905) 2025-09-13 13:55:53 +08:00
2dust 0f3124bece up 1.10.23 2025-09-11 19:09:59 +08:00
2dust 2614f72a36 Update AndroidLibXrayLite 2025-09-11 19:08:56 +08:00
2dust 480c7fc8a1 up 1.10.22 2025-09-05 18:37:08 +08:00
2dust 302e7f148e Update libs.versions.toml 2025-09-05 18:36:42 +08:00
2dust 98d6da983b Update AndroidLibXrayLite 2025-09-05 18:36:27 +08:00
2dust f6f60f77d3 up 1.10.21 2025-09-01 09:41:04 +08:00
2dust 7be0fb3ecd Update AndroidLibXrayLite 2025-09-01 09:40:27 +08:00
2dust d53a9f9ecd up 1.10.20 2025-08-30 11:20:23 +08:00
2dust 9eb03eece2 Update AndroidLibXrayLite 2025-08-30 11:12:44 +08:00
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
https://github.com/2dust/v2rayNG/commit/f305e26a395650301e5565d95ffb1d3199e846ed
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
84 changed files with 2277 additions and 762 deletions
+10 -10
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5
with:
submodules: 'recursive'
fetch-depth: '0'
@@ -31,31 +31,31 @@ jobs:
- name: Install NDK
run: |
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
--channel=3 \
--install "ndk;29.0.13113456"
echo "NDK_HOME=$ANDROID_HOME/ndk/29.0.13113456" >> $GITHUB_ENV
--channel=0 \
--install "ndk;28.2.13676358"
echo "NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> $GITHUB_ENV
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
id: cache-libtun2socks-restore
uses: actions/cache/restore@v4
with:
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') }}-${{ hashFiles('compile-tun2socks.sh') }}--${{ hashFiles('compile-hevtun.sh') }}
- name: Build libtun2socks
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
run: |
bash compile-tun2socks.sh
bash compile-tun2socks.sh && bash compile-hevtun.sh
- name: Save libtun2socks
if: steps.cache-libtun2socks-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
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') }}-${{ hashFiles('compile-tun2socks.sh') }}--${{ hashFiles('compile-hevtun.sh') }}
- name: Copy libtun2socks
run: |
@@ -153,7 +153,7 @@ jobs:
uses: svenstaro/upload-release-action@v2
if: github.event.inputs.release_tag != ''
with:
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*playstore*/release/*.apk
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*.apk
tag: ${{ github.event.inputs.release_tag }}
file_glob: true
prerelease: true
+3
View File
@@ -10,3 +10,6 @@
[submodule "libancillary"]
path = libancillary
url = https://github.com/shadowsocks/libancillary
[submodule "hev-socks5-tunnel"]
path = hev-socks5-tunnel
url = https://github.com/heiher/hev-socks5-tunnel
+1 -5
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)
[![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.3.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)
[![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)
[![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
[github_2dust](https://t.me/github_2dust)
+7 -5
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 652
versionName = "1.10.2"
versionCode = 684
versionName = "1.10.32"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
@@ -67,14 +67,16 @@ android {
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
applicationVariants.all {
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">v2rayNG (F-Droid)</string>
</resources>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_qu_switch_24dp"
android:shortcutDisabledMessage="@string/app_widget_name"
android:shortcutId="shortcuts_switch"
android:shortcutLongLabel="@string/app_widget_name"
android:shortcutShortLabel="@string/app_widget_name">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.v2ray.ang.ui.ScSwitchActivity"
android:targetPackage="com.v2ray.ang.fdroid" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_qu_scan_24dp"
android:shortcutDisabledMessage="@string/menu_item_import_config_qrcode"
android:shortcutId="shortcuts_scan"
android:shortcutLongLabel="@string/menu_item_import_config_qrcode"
android:shortcutShortLabel="@string/menu_item_import_config_qrcode">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.v2ray.ang.ui.ScScannerActivity"
android:targetPackage="com.v2ray.ang.fdroid" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>
+5 -2
View File
@@ -144,6 +144,9 @@
<data android:host="install-sub" />
</intent-filter>
</activity>
<activity
android:name=".ui.CheckUpdateActivity"
android:exported="false" />
<activity
android:name=".ui.AboutActivity"
android:exported="false" />
@@ -192,8 +195,8 @@
android:resource="@xml/app_widget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.v2ray.ang.action.widget.click" />
<action android:name="com.v2ray.ang.action.activity" />
<action android:name="${applicationId}.action.widget.click" />
<action android:name="${applicationId}.action.activity" />
</intent-filter>
</receiver>
<receiver
@@ -6,20 +6,19 @@
"bittorrent"
]
},
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "代理Google",
"outboundTag": "proxy",
"domain": [
"geosite:google"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
@@ -139,4 +138,4 @@
"port": "0-65535",
"outboundTag": "direct"
}
]
]
@@ -1,18 +1,17 @@
[
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "代理Google",
"outboundTag": "proxy",
"domain": [
"geosite:google"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
@@ -93,4 +92,4 @@
"geosite:cn"
]
}
]
]
@@ -26,6 +26,8 @@ object AppConfig {
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_VPN_DNS = "pref_vpn_dns"
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_RULESET = "pref_routing_ruleset"
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_DELAY_TEST_URL = "pref_delay_test_url"
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_IS_BOOTED = "pref_is_booted"
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_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_v2"
/** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -68,9 +75,9 @@ object AppConfig {
const val PROTOCOL_FREEDOM = "freedom"
/** Broadcast actions. */
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
const val BROADCAST_ACTION_SERVICE = "$ANG_PACKAGE.action.service"
const val BROADCAST_ACTION_ACTIVITY = "$ANG_PACKAGE.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "$ANG_PACKAGE.action.widget.click"
/** Tasker extras. */
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
@@ -84,6 +91,8 @@ object AppConfig {
const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block"
const val TAG_FRAGMENT = "fragment"
const val TAG_DNS = "dns-module"
const val TAG_DOMESTIC_DNS = "domestic-dns"
/** Network-related constants. */
const val UPLINK = "uplink"
@@ -103,7 +112,8 @@ object AppConfig {
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_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"
const val IP_API_URL = "https://api.ip.sb/geoip"
/** DNS server addresses. */
const val DNS_PROXY = "1.1.1.1"
@@ -160,6 +170,10 @@ object AppConfig {
/** Give a good name to this, IDK*/
const val VPN = "VPN"
const val VPN_MTU = 1500
/** hev-sock5-tunnel read-write-timeout value */
const val HEVTUN_RW_TIMEOUT = "300,60"
// Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"
@@ -168,7 +182,9 @@ object AppConfig {
// Android Private DNS constants
const val DNS_DNSPOD_DOMAIN = "dot.pub"
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_QUAD9_DOMAIN = "dns.quad9.net"
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
@@ -182,14 +198,16 @@ object AppConfig {
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_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_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_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
val BYPASS_PRIVATE_IP_LIST = arrayListOf(
val ROUTED_IP_LIST = arrayListOf(
"0.0.0.0/5",
"8.0.0.0/7",
"11.0.0.0/8",
@@ -2,10 +2,11 @@ package com.v2ray.ang.dto
data class IPAPIInfo(
var ip: String? = null,
var city: String? = null,
var region: String? = null,
var region_code: String? = null,
var clientIp: String? = null,
var ip_addr: String? = null,
var query: String? = null,
var country: String? = null,
var country_name: String? = null,
var country_code: String? = null
var country_code: String? = null,
var countryCode: String? = null
)
@@ -44,6 +44,7 @@ data class ProfileItem(
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = null,
var mldsa65Verify: String? = null,
var secretKey: String? = null,
var preSharedKey: String? = null,
@@ -11,6 +11,8 @@ data class SubscriptionItem(
var prevProfile: String? = null,
var nextProfile: String? = null,
var filter: String? = null,
var intelligentSelectionFilter: String? = null,
var allowInsecureUrl: Boolean = false,
var userAgent: String? = null,
)
@@ -245,7 +245,14 @@ data class V2rayConfig(
var tproxy: String? = null,
var mark: Int? = 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(
@@ -264,7 +271,8 @@ data class V2rayConfig(
val show: Boolean = false,
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = null
var spiderX: String? = null,
var mldsa65Verify: String? = null
)
data class QuicSettingBean(
@@ -489,6 +497,7 @@ data class V2rayConfig(
var expectIPs: List<String>? = null,
val clientIp: String? = null,
val skipFallback: Boolean? = null,
val tag: String? = null,
)
}
@@ -496,14 +505,14 @@ data class V2rayConfig(
var domainStrategy: String,
var domainMatcher: String? = null,
var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null
var balancers: List<BalancerBean>? = null
) {
data class RulesBean(
var type: String = "field",
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
var outboundTag: String? = null,
var balancerTag: String? = null,
var port: String? = null,
val sourcePort: String? = null,
@@ -515,6 +524,32 @@ data class V2rayConfig(
val attrs: 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(
@@ -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(
var ipPool: String = "198.18.0.0/15",
var poolSize: Int = 10000
@@ -15,5 +15,6 @@ data class VmessQRCode(
var tls: String = "",
var sni: String = "",
var alpn: String = "",
var fp: String = ""
var fp: String = "",
var insecure: String = ""
)
@@ -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
}
}
}
}
@@ -4,6 +4,8 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.NetworkType
import com.v2ray.ang.dto.ProfileItem
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 java.net.URI
@@ -26,7 +28,7 @@ open class FmtBase {
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(config.server),
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
config.serverPort
)
@@ -70,10 +72,12 @@ open class FmtBase {
if (config.security != AppConfig.TLS && config.security != AppConfig.REALITY) {
config.security = null
}
config.insecure = if (queryParam["allowInsecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["allowInsecure"].orEmpty() == "1"
// Support multiple possible query keys for allowInsecure like the C# implementation
val allowInsecureKeys = arrayOf("insecure", "allowInsecure", "allow_insecure")
config.insecure = when {
allowInsecureKeys.any { queryParam[it] == "1" } -> true
allowInsecureKeys.any { queryParam[it] == "0" } -> false
else -> allowInsecure
}
config.sni = queryParam["sni"]
config.fingerPrint = queryParam["fp"]
@@ -81,6 +85,7 @@ open class FmtBase {
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.mldsa65Verify = queryParam["pqv"]
config.flow = queryParam["flow"]
}
@@ -99,7 +104,14 @@ open class FmtBase {
config.publicKey.let { if (it.isNotNullEmpty()) dicQuery["pbk"] = it.orEmpty() }
config.shortId.let { if (it.isNotNullEmpty()) dicQuery["sid"] = 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() }
// Add two keys for compatibility: "insecure" and "allowInsecure"
if (config.security == AppConfig.TLS) {
val insecureFlag = if (config.insecure == true) "1" else "0"
dicQuery["insecure"] = insecureFlag
dicQuery["allowInsecure"] = insecureFlag
}
val networkType = NetworkType.fromString(config.network)
dicQuery["type"] = networkType.type
@@ -149,6 +161,20 @@ open class FmtBase {
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()
}
}
@@ -17,7 +17,7 @@ object HttpFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
@@ -34,17 +34,14 @@ object Hysteria2Fmt : FmtBase() {
if (!uri.rawQuery.isNullOrEmpty()) {
val queryParam = getQueryParam(uri)
config.security = queryParam["security"] ?: AppConfig.TLS
config.insecure = if (queryParam["insecure"].isNullOrEmpty()) {
allowInsecure
} else {
queryParam["insecure"].orEmpty() == "1"
}
config.sni = queryParam["sni"]
config.alpn = queryParam["alpn"]
getItemFormQuery(config, queryParam, allowInsecure)
config.security = queryParam["security"] ?: AppConfig.TLS
config.obfsPassword = queryParam["obfs-password"]
config.portHopping = queryParam["mport"]
if (config.portHopping.isNotNullEmpty()) {
config.portHoppingInterval = queryParam["mportHopInt"]
}
config.pinSHA256 = queryParam["pinSHA256"]
}
@@ -73,6 +70,9 @@ object Hysteria2Fmt : FmtBase() {
if (config.portHopping.isNotNullEmpty()) {
dicQuery["mport"] = config.portHopping.orEmpty()
}
if (config.portHoppingInterval.isNotNullEmpty()) {
dicQuery["mportHopInt"] = config.portHoppingInterval.orEmpty()
}
if (config.pinSHA256.isNotNullEmpty()) {
dicQuery["pinSHA256"] = config.pinSHA256.orEmpty()
}
@@ -122,7 +122,7 @@ object ShadowsocksFmt : FmtBase() {
fun toUri(config: ProfileItem): String {
val pw = "${config.method}:${config.password}"
return toUri(config, Utils.encode(pw), null)
return toUri(config, Utils.encode(pw, true), null)
}
/**
@@ -135,7 +135,7 @@ object ShadowsocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.method = profileItem.method
@@ -51,7 +51,7 @@ object SocksFmt : FmtBase() {
else
":"
return toUri(config, Utils.encode(pw), null)
return toUri(config, Utils.encode(pw, true), null)
}
/**
@@ -64,7 +64,7 @@ object SocksFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
@@ -64,7 +64,7 @@ object TrojanFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN)
outboundBean?.settings?.servers?.first()?.let { server ->
server.address = profileItem.server.orEmpty()
server.address = getServerAddress(profileItem)
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.flow = profileItem.flow
@@ -60,7 +60,7 @@ object VlessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].encryption = profileItem.method
@@ -28,7 +28,7 @@ object VmessFmt : FmtBase() {
return parseVmessStd(str)
}
var allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val allowInsecure = MmkvManager.decodeSettingsBool(AppConfig.PREF_ALLOW_INSECURE, false)
val config = ProfileItem.create(EConfigType.VMESS)
var result = str.replace(EConfigType.VMESS.protocolScheme, "")
@@ -78,12 +78,15 @@ object VmessFmt : FmtBase() {
else -> {}
}
config.security = vmessQRCode.tls
config.insecure = allowInsecure
config.security = vmessQRCode.tls
config.sni = vmessQRCode.sni
config.fingerPrint = vmessQRCode.fp
config.alpn = vmessQRCode.alpn
config.insecure = when (vmessQRCode.insecure) {
"1" -> true
"0" -> false
else -> allowInsecure
}
return config
}
@@ -132,6 +135,11 @@ object VmessFmt : FmtBase() {
vmessQRCode.sni = config.sni.orEmpty()
vmessQRCode.fp = config.fingerPrint.orEmpty()
vmessQRCode.alpn = config.alpn.orEmpty()
vmessQRCode.insecure = when (config.insecure) {
true -> "1"
false -> "0"
else -> ""
}
val json = JsonUtil.toJson(vmessQRCode)
return Utils.encode(json)
@@ -172,7 +180,7 @@ object VmessFmt : FmtBase() {
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
vnext.address = profileItem.server.orEmpty()
vnext.address = getServerAddress(profileItem)
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].security = profileItem.method
@@ -71,7 +71,7 @@ object AngConfigManager {
if (sb.count() > 0) {
Utils.setClipboard(context, sb.toString())
}
return sb.lines().count()
return sb.lines().count() - 1
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to share non-custom configs to clipboard", e)
return -1
@@ -415,7 +415,7 @@ object AngConfigManager {
if (!it.second.enabled) {
return 0
}
val url = HttpUtil.idnToASCII(it.second.url)
val url = HttpUtil.toIdnUrl(it.second.url)
if (!Utils.isValidUrl(url)) {
return 0
}
@@ -425,17 +425,18 @@ object AngConfigManager {
}
}
Log.i(AppConfig.TAG, url)
val userAgent = it.second.userAgent
var configText = try {
val httpPort = SettingsManager.getHttpPort()
HttpUtil.getUrlContentWithUserAgent(url, 15000, httpPort)
HttpUtil.getUrlContentWithUserAgent(url, userAgent, 15000, httpPort)
} catch (e: Exception) {
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
""
}
if (configText.isEmpty()) {
configText = try {
HttpUtil.getUrlContentWithUserAgent(url)
HttpUtil.getUrlContentWithUserAgent(url, userAgent)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
""
@@ -490,4 +491,31 @@ object AngConfigManager {
MmkvManager.encodeSubscription("", subItem)
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
}
}
@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.app.Notification
import android.app.NotificationChannel
@@ -12,12 +12,10 @@ import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
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.dto.ProfileItem
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -27,7 +25,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.math.min
object NotificationService {
object NotificationManager {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
@@ -50,7 +48,7 @@ object NotificationService {
lastQueryTime = System.currentTimeMillis()
var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
outboundTags?.remove(AppConfig.TAG_DIRECT)
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
@@ -66,15 +64,15 @@ object NotificationService {
proxyTotal += up + down
}
}
val directUplink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.DOWNLINK)
val directUplink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK)
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds
)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
@@ -102,12 +100,12 @@ object NotificationService {
val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = ANG_PACKAGE
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
restartV2RayIntent.`package` = ANG_PACKAGE
restartV2RayIntent.`package` = AppConfig.ANG_PACKAGE
restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)
@@ -1,4 +1,4 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import android.content.Context
import android.os.SystemClock
@@ -7,11 +7,12 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import java.io.File
object PluginUtil {
object PluginServiceManager {
private const val HYSTERIA2 = "libhysteria2.so"
private val procService: ProcessService by lazy {
@@ -28,13 +29,17 @@ object PluginUtil {
fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) {
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")
return
}
try {
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")
val configFile = genConfigHy2(context, config, socksPort) ?: return
val cmd = genCmdHy2(context, configFile)
@@ -133,4 +138,4 @@ object PluginUtil {
Log.e(AppConfig.TAG, "Failed to stop Hysteria2 process", e)
}
}
}
}
@@ -16,6 +16,7 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RoutingType
import com.v2ray.ang.dto.RulesetItem
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.decodeServerList
import com.v2ray.ang.util.JsonUtil
@@ -159,7 +160,7 @@ object SettingsManager {
* @return True if bypassing LAN, false otherwise.
*/
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") {
return true
} else if (vpnBypassLan == "2") {
@@ -356,4 +357,24 @@ object SettingsManager {
"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)
}
}
@@ -168,10 +168,13 @@ object SpeedtestManager {
fun getRemoteIPInfo(): String? {
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
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"
}
/**
@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.annotation.SuppressLint
import android.app.NotificationChannel
@@ -11,11 +11,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
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.handler.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.handler.MmkvManager
object SubscriptionUpdater {
@@ -24,7 +20,7 @@ object SubscriptionUpdater {
private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification =
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL)
NotificationCompat.Builder(applicationContext, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
.setWhen(0)
.setTicker("Update")
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
@@ -46,18 +42,18 @@ object SubscriptionUpdater {
val subItem = sub.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
notification.setChannelId(AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
val channel =
NotificationChannel(
SUBSCRIPTION_UPDATE_CHANNEL,
SUBSCRIPTION_UPDATE_CHANNEL_NAME,
AppConfig.SUBSCRIPTION_UPDATE_CHANNEL,
AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_MIN
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(3, notification.build())
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}")
}
notificationManager.cancel(3)
@@ -17,7 +17,6 @@ import java.io.FileOutputStream
object UpdateCheckerManager {
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
try {
val url = if (includePreRelease) {
AppConfig.APP_API_URL
} else {
@@ -53,10 +52,6 @@ object UpdateCheckerManager {
} else {
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) {
@@ -105,8 +100,19 @@ object UpdateCheckerManager {
}
private fun getDownloadUrl(release: GitHubRelease, abi: String): String {
return release.assets.find { it.name.contains(abi) }?.browserDownloadUrl
?: release.assets.firstOrNull()?.browserDownloadUrl
val fDroid = "fdroid"
val assetsByAbi = release.assets.filter {
(it.name.contains(abi, true))
}
val asset = if (BuildConfig.APPLICATION_ID.contains(fDroid, ignoreCase = true)) {
assetsByAbi.firstOrNull { it.name.contains(fDroid) }
} else {
assetsByAbi.firstOrNull { !it.name.contains(fDroid) }
}
return asset?.browserDownloadUrl
?: throw IllegalStateException("No compatible APK found")
}
}
@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.app.Service
import android.content.BroadcastReceiver
@@ -13,12 +13,11 @@ import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.service.ServiceControl
import com.v2ray.ang.service.V2RayProxyOnlyService
import com.v2ray.ang.service.V2RayVpnService
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 go.Seq
import kotlinx.coroutines.CoroutineScope
@@ -102,7 +101,7 @@ object V2RayServiceManager {
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (config.configType != EConfigType.CUSTOM
&& !Utils.isValidUrl(config.server)
&& !Utils.isIpAddress(config.server)
&& !Utils.isPureIpAddress(config.server.orEmpty())
) return
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
// if (!result.status) return
@@ -163,16 +162,16 @@ object V2RayServiceManager {
if (coreController.isRunning == false) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
return false
}
try {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
NotificationService.showNotification(currentConfig)
NotificationService.startSpeedNotification(currentConfig)
NotificationManager.showNotification(currentConfig)
NotificationManager.startSpeedNotification(currentConfig)
PluginUtil.runPlugin(service, config, result.socksPort)
PluginServiceManager.runPlugin(service, config, result.socksPort)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to startup service", e)
return false
@@ -199,14 +198,14 @@ object V2RayServiceManager {
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
}
PluginUtil.stopPlugin()
PluginServiceManager.stopPlugin()
return true
}
@@ -364,14 +363,14 @@ object V2RayServiceManager {
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats")
NotificationService.stopSpeedNotification(currentConfig)
NotificationManager.stopSpeedNotification(currentConfig)
}
Intent.ACTION_SCREEN_ON -> {
Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats")
NotificationService.startSpeedNotification(currentConfig)
NotificationManager.startSpeedNotification(currentConfig)
}
}
}
}
}
}
@@ -4,6 +4,7 @@ import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.EConfigType
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.RoutingBean.RulesBean
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.HttpFmt
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt
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.
*
@@ -98,7 +120,7 @@ object V2rayConfigManager {
val result = ConfigResult(false)
val address = config.server ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result
@@ -132,7 +154,10 @@ object V2rayConfigManager {
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.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
@@ -140,6 +165,80 @@ object V2rayConfigManager {
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.
*
@@ -152,7 +251,7 @@ object V2rayConfigManager {
val result = ConfigResult(false)
val address = config.server ?: return result
if (!Utils.isIpAddress(address)) {
if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
Log.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result
@@ -371,27 +470,49 @@ object V2rayConfigManager {
)
}
// DNS inbound
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"
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL ,true) == false) {
val localDnsPort = Utils.parseInt(
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
// DNS inbound
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(
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(
V2rayConfig.InboundBean(
tag = "dns-in",
port = localDnsPort,
listen = AppConfig.LOOPBACK,
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null
} else {
//hev-socks5-tunnel dns routing
v2rayConfig.routing.rules.add(
0, RulesBean(
inboundTag = arrayListOf("socks"),
outboundTag = "dns-out",
port = "53",
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) {
Log.e(AppConfig.TAG, "Failed to configure custom local DNS", e)
return false
@@ -463,22 +575,31 @@ object V2rayConfigManager {
address = domesticDns.first(),
domains = directDomain,
expectIPs = if (isCnRoutingMode) geoipCn else null,
skipFallback = true
skipFallback = true,
tag = AppConfig.TAG_DOMESTIC_DNS
)
)
}
if (Utils.isPureIpAddress(domesticDns.first())) {
v2rayConfig.routing.rules.add(
0, RulesBean(
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null
)
)
//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_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
try {
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
@@ -493,41 +614,28 @@ object V2rayConfigManager {
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
v2rayConfig.dns = V2rayConfig.DnsBean(
servers = servers,
hosts = hosts
hosts = hosts,
tag = AppConfig.TAG_DNS
)
// DNS routing
if (Utils.isPureIpAddress(remoteDns.first())) {
v2rayConfig.routing.rules.add(
0, RulesBean(
outboundTag = AppConfig.TAG_PROXY,
port = "53",
ip = arrayListOf(remoteDns.first()),
domain = null
)
v2rayConfig.routing.rules.add(
RulesBean(
outboundTag = AppConfig.TAG_DIRECT,
inboundTag = arrayListOf(AppConfig.TAG_DOMESTIC_DNS),
domain = null
)
}
)
v2rayConfig.routing.rules.add(
RulesBean(
outboundTag = AppConfig.TAG_PROXY,
inboundTag = arrayListOf(AppConfig.TAG_DNS),
domain = null
)
)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to configure DNS", e)
return false
@@ -738,6 +846,78 @@ object V2rayConfigManager {
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.
*
@@ -828,12 +1008,24 @@ object V2rayConfigManager {
for (item in proxyOutboundList) {
val domain = item.getServerAddress()
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)
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) {
resolvedIps[0]
} else {
@@ -1045,12 +1237,21 @@ object V2rayConfigManager {
fun populateTlsSettings(streamSettings: StreamSettingsBean, profileItem: ProfileItem, sniExt: String?) {
val streamSecurity = profileItem.security.orEmpty()
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 alpns = profileItem.alpn
val publicKey = profileItem.publicKey
val shortId = profileItem.shortId
val spiderX = profileItem.spiderX
val mldsa65Verify = profileItem.mldsa65Verify
streamSettings.security = if (streamSecurity.isEmpty()) null else streamSecurity
if (streamSettings.security == null) return
@@ -1062,6 +1263,7 @@ object V2rayConfigManager {
publicKey = if (publicKey.isNullOrEmpty()) null else publicKey,
shortId = if (shortId.isNullOrEmpty()) null else shortId,
spiderX = if (spiderX.isNullOrEmpty()) null else spiderX,
mldsa65Verify = if (mldsa65Verify.isNullOrEmpty()) null else mldsa65Verify,
)
if (streamSettings.security == AppConfig.TLS) {
streamSettings.tlsSettings = tlsSetting
@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class BootReceiver : BroadcastReceiver() {
/**
@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class TaskerReceiver : BroadcastReceiver() {
@@ -10,7 +10,7 @@ import android.os.Build
import android.widget.RemoteViews
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class WidgetProvider : AppWidgetProvider() {
/**
@@ -13,6 +13,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference
@@ -25,14 +26,13 @@ class QSTileService : TileService() {
* @param state The state to set.
*/
fun setState(state: Int) {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
if (state == Tile.STATE_INACTIVE) {
qsTile?.state = Tile.STATE_INACTIVE
qsTile?.label = getString(R.string.app_name)
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
} else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = V2RayServiceManager.getRunningServerName()
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
}
qsTile?.updateTile()
@@ -45,7 +45,11 @@ class QSTileService : TileService() {
override fun onStartListening() {
super.onStartListening()
setState(Tile.STATE_INACTIVE)
if (V2RayServiceManager.isRunning()) {
setState(Tile.STATE_ACTIVE)
} else {
setState(Tile.STATE_INACTIVE)
}
mMsgReceive = ReceiveMessageHandler(this)
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
@@ -0,0 +1,100 @@
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'")
// Read-write timeout settings
val timeoutSetting = MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT) ?: AppConfig.HEVTUN_RW_TIMEOUT
val parts = timeoutSetting.split(",")
.map { it.trim() }
.filter { it.isNotEmpty() }
val tcpTimeout = parts.getOrNull(0)?.toIntOrNull() ?: 300
val udpTimeout = parts.getOrNull(1)?.toIntOrNull() ?: 60
appendLine("misc:")
appendLine(" tcp-read-write-timeout: ${tcpTimeout * 1000}")
appendLine(" udp-read-write-timeout: ${udpTimeout * 1000}")
appendLine(" log-level: ${MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) ?: "warn"}")
}
}
/**
* 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)
}
}
}
@@ -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()
}
@@ -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)
}
}
}
@@ -7,6 +7,7 @@ import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper
import java.lang.ref.SoftReference
@@ -9,10 +9,10 @@ import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.PluginServiceManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils
import go.Seq
import kotlinx.coroutines.CoroutineScope
@@ -78,7 +78,7 @@ class V2RayTestService : Service() {
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (config.configType == EConfigType.HYSTERIA2) {
val delay = PluginUtil.realPingHy2(this, config)
val delay = PluginServiceManager.realPingHy2(this, config)
return delay
} else {
val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(this, guid)
@@ -5,8 +5,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
@@ -21,29 +19,17 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.NotificationManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper
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
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 var isRunning = false
private lateinit var process: Process
private var tun2SocksService: Tun2SocksControl? = null
/**destroy
* 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() {
super.onDestroy()
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -116,7 +102,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
override fun startService() {
setup()
setupService()
}
override fun stopService() {
@@ -139,13 +125,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Sets up the VPN service.
* Prepares the VPN and configures it if preparation is successful.
*/
private fun setup() {
private fun setupService() {
val prepare = prepare(this)
if (prepare != null) {
return
}
if (setupVpnService() != true) {
if (configureVpnService() != true) {
return
}
@@ -156,18 +142,54 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Configures the VPN service.
* @return True if the VPN service was configured successfully, false otherwise.
*/
private fun setupVpnService(): Boolean {
// If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters.
private fun configureVpnService(): Boolean {
val builder = Builder()
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
builder.setMtu(VPN_MTU)
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
// Configure network settings (addresses, routing and DNS)
configureNetworkSettings(builder)
// 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()
// Configure IPv4 settings
builder.setMtu(SettingsManager.getVpnMtu())
builder.addAddress(vpnConfig.ipv4Client, 30)
// Configure routing rules
if (bypassLan) {
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach {
AppConfig.ROUTED_IP_LIST.forEach {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
@@ -175,55 +197,37 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.addRoute("0.0.0.0", 0)
}
// Configure IPv6 if enabled
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
builder.addAddress(vpnConfig.ipv6Client, 126)
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 {
builder.addRoute("::", 0)
}
}
// if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
// } else {
SettingsManager.getVpnDnsServers()
.forEach {
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
// Configure DNS servers
//if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
//} else {
SettingsManager.getVpnDnsServers().forEach {
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
// }
}
builder.setSession(V2RayServiceManager.getRunningServerName())
}
val selfPackageName = BuildConfig.APPLICATION_ID
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)) {
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
//process self package
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
apps?.forEach {
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
}
/**
* Configures platform-specific VPN features for different Android versions.
*
* @param builder The VPN Builder to configure
*/
private fun configurePlatformFeatures(builder: Builder) {
// Android P (API 28) and above: Configure network callbacks
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
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) {
builder.setMetered(false)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY)) {
builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOOPBACK, SettingsManager.getHttpPort()))
}
}
}
// Create a new interface using the builder and save the parameters.
try {
mInterface = builder.establish()!!
isRunning = true
return true
} catch (e: Exception) {
// non-nullable lateinit var
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e)
stopV2Ray()
/**
* Configures per-app proxy rules for the VPN builder.
*
* - If per-app proxy is not enabled, disallow the VPN service's own package.
* - If no apps are selected, disallow the VPN service's own package.
* - If bypass mode is enabled, disallow all selected apps (including self).
* - If proxy mode is enabled, only allow the selected apps (excluding self).
*
* @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.
*/
private fun runTun2socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
val socksPort = SettingsManager.getSocksPort()
val cmd = arrayListOf(
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "$LOOPBACK:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
"--enable-udprelay",
"--loglevel", "notice"
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL, true) == true) {
tun2SocksService = TProxyService(
context = applicationContext,
vpnInterface = mInterface,
isRunningProvider = { isRunning },
restartCallback = { runTun2socks() }
)
} else {
tun2SocksService = Tun2SocksService(
context = applicationContext,
vpnInterface = mInterface,
isRunningProvider = { isRunning },
restartCallback = { runTun2socks() }
)
}
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 {
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
}
}
tun2SocksService?.startTun2Socks()
}
/**
@@ -350,12 +332,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
try {
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy")
process.destroy()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
tun2SocksService?.stopTun2Socks()
tun2SocksService = null
V2RayServiceManager.stopCoreLoop()
@@ -375,3 +353,4 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
}
@@ -5,28 +5,21 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding
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.AppManagerUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
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 {
Utils.openUri(this, AppConfig.APP_URL)
}
@@ -151,6 +127,9 @@ class AboutActivity : BaseActivity() {
"v${BuildConfig.VERSION_NAME} (${SpeedtestManager.getLibVersion()})".also {
binding.tvVersion.text = it
}
BuildConfig.APPLICATION_ID.also {
binding.tvAppId.text = it
}
}
private fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
@@ -222,28 +201,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()
}
}
@@ -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()
}
}
@@ -38,7 +38,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MigrateManager
import com.v2ray.ang.handler.MmkvManager
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.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers
@@ -211,11 +211,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
if (isRunning) {
binding.fab.setImageResource(R.drawable.ic_stop_24dp)
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_active))
binding.fab.contentDescription = getString(R.string.action_stop_service)
setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true
} else {
binding.fab.setImageResource(R.drawable.ic_play_24dp)
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_inactive))
binding.fab.contentDescription = getString(R.string.tasker_start_service)
setTestState(getString(R.string.connection_not_connected))
binding.layoutTest.isFocusable = false
}
@@ -385,6 +387,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
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 -> {
restartV2Ray()
true
@@ -685,6 +695,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
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.check_for_update -> startActivity(Intent(this, CheckUpdateActivity::class.java))
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
}
@@ -26,7 +26,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.ItemTouchHelperAdapter
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.delay
import kotlinx.coroutines.launch
@@ -201,6 +201,11 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
private fun showQRCode(guid: String) {
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
if (share_method.isNotEmpty()) {
ivBinding.ivQcode.contentDescription = share_method[0]
} else {
ivBinding.ivQcode.contentDescription = "QR Code"
}
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
}
@@ -42,39 +42,7 @@ class PerAppProxyActivity : BaseActivity() {
addCustomDividerToRecyclerView(binding.recyclerView, this, R.drawable.custom_divider)
lifecycleScope.launch {
try {
binding.pbWaiting.show()
val blacklist = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val apps = withContext(Dispatchers.IO) {
val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity)
if (blacklist != null) {
appsList.forEach { app ->
app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0
}
appsList.sortedWith { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
}
}
} else {
val collator = Collator.getInstance()
appsList.sortedWith(compareBy(collator) { it.appName })
}
}
appsAll = apps
adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist)
binding.recyclerView.adapter = adapter
binding.pbWaiting.hide()
} catch (e: Exception) {
binding.pbWaiting.hide()
Log.e(ANG_PACKAGE, "Error loading apps", e)
}
}
initList()
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
MmkvManager.encodeSettings(AppConfig.PREF_PER_APP_PROXY, isChecked)
@@ -91,6 +59,50 @@ class PerAppProxyActivity : BaseActivity() {
}
}
private fun initList() {
binding.pbWaiting.show()
lifecycleScope.launch {
try {
val blacklist =
MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val apps = withContext(Dispatchers.IO) {
val appsList = AppManagerUtil.loadNetworkAppList(this@PerAppProxyActivity)
if (blacklist != null) {
appsList.forEach { app ->
app.isSelected = if (blacklist.contains(app.packageName)) 1 else 0
}
appsList.sortedWith { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected < p2.isSelected -> 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 {
val collator = Collator.getInstance()
appsList.sortedWith(compareBy(collator) { it.appName })
}
}
appsAll = apps
adapter = PerAppProxyAdapter(this@PerAppProxyActivity, apps, blacklist)
binding.recyclerView.adapter = adapter
} catch (e: Exception) {
Log.e(ANG_PACKAGE, "Error loading apps", e)
} finally {
binding.pbWaiting.hide()
}
}
}
override fun onPause() {
super.onPause()
adapter?.let {
@@ -114,14 +126,40 @@ class PerAppProxyActivity : BaseActivity() {
})
}
return super.onCreateOptionsMenu(menu)
}
@SuppressLint("NotifyDataSetChanged")
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.select_all -> adapter?.let { it ->
R.id.select_all -> {
selectAllApp()
allowPerAppProxy()
true
}
R.id.select_proxy_app -> {
selectProxyAppAuto()
allowPerAppProxy()
true
}
R.id.import_proxy_app -> {
importProxyApp()
allowPerAppProxy()
true
}
R.id.export_proxy_app -> {
exportProxyApp()
true
}
else -> super.onOptionsItemSelected(item)
}
private fun selectAllApp() {
adapter?.let { it ->
val pkgNames = it.apps.map { it.packageName }
if (it.blacklist.containsAll(pkgNames)) {
it.apps.forEach {
@@ -136,27 +174,10 @@ class PerAppProxyActivity : BaseActivity() {
}
it.notifyDataSetChanged()
true
} == true
R.id.select_proxy_app -> {
selectProxyApp()
true
}
R.id.import_proxy_app -> {
importProxyApp()
true
}
R.id.export_proxy_app -> {
exportProxyApp()
true
}
else -> super.onOptionsItemSelected(item)
}
private fun selectProxyApp() {
private fun selectProxyAppAuto() {
toast(R.string.msg_downloading_content)
binding.pbWaiting.show()
@@ -193,6 +214,10 @@ class PerAppProxyActivity : BaseActivity() {
toastSuccess(R.string.toast_success)
}
private fun allowPerAppProxy() {
binding.switchPerAppProxy.isChecked = true
}
@SuppressLint("NotifyDataSetChanged")
private fun selectProxyApp(content: String, force: Boolean): Boolean {
try {
@@ -2,7 +2,7 @@ package com.v2ray.ang.ui
import android.os.Bundle
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class ScSwitchActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -117,6 +117,8 @@ class ServerActivity : BaseActivity() {
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 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_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) }
@@ -253,9 +255,14 @@ class ServerActivity : BaseActivity() {
// Case 1: Null or blank
isBlank -> {
listOf(
container_sni, container_fingerprint, container_alpn,
container_allow_insecure, container_public_key,
container_short_id, container_spider_x
container_sni,
container_fingerprint,
container_alpn,
container_allow_insecure,
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
@@ -270,7 +277,8 @@ class ServerActivity : BaseActivity() {
listOf(
container_public_key,
container_short_id,
container_spider_x
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
@@ -284,7 +292,8 @@ class ServerActivity : BaseActivity() {
listOf(
container_public_key,
container_short_id,
container_spider_x
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.VISIBLE }
}
}
@@ -366,9 +375,12 @@ class ServerActivity : BaseActivity() {
if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure)
}
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
listOf(
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
} else if (config.security == REALITY) {
container_public_key?.visibility = View.VISIBLE
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())
container_spider_x?.visibility = View.VISIBLE
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
}
}
if (config.security.isNullOrEmpty()) {
container_sni?.visibility = View.GONE
container_fingerprint?.visibility = View.GONE
container_alpn?.visibility = View.GONE
container_allow_insecure?.visibility = View.GONE
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
listOf(
container_sni,
container_fingerprint,
container_alpn,
container_allow_insecure,
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
val network = Utils.arrayFind(networks, config.network.orEmpty())
if (network >= 0) {
@@ -550,6 +567,7 @@ class ServerActivity : BaseActivity() {
val publicKey = et_public_key?.text?.toString()
val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString()
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
@@ -566,6 +584,7 @@ class ServerActivity : BaseActivity() {
config.publicKey = publicKey
config.shortId = shortId
config.spiderX = spiderX
config.mldsa65Verify = mldsa65Verify
}
private fun transportTypes(network: String?): Array<out String> {
@@ -18,7 +18,7 @@ import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R
import com.v2ray.ang.extension.toLongEx
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.viewmodel.SettingsViewModel
import java.util.concurrent.TimeUnit
@@ -37,13 +37,15 @@ class SettingsActivity : BaseActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
// private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val appendHttpProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_APPEND_HTTP_PROXY) }
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 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 muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
@@ -65,14 +67,18 @@ class SettingsActivity : BaseActivity() {
private val delayTestUrl by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DELAY_TEST_URL) }
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?) {
addPreferencesFromResource(R.xml.pref_settings)
perAppProxy?.setOnPreferenceClickListener {
startActivity(Intent(activity, PerAppProxyActivity::class.java))
perAppProxy?.isChecked = true
false
}
// perAppProxy?.setOnPreferenceClickListener {
// startActivity(Intent(activity, PerAppProxyActivity::class.java))
// perAppProxy?.isChecked = true
// false
// }
localDns?.setOnPreferenceChangeListener { _, any ->
updateLocalDns(any as Boolean)
true
@@ -88,6 +94,12 @@ class SettingsActivity : BaseActivity() {
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 ->
updateMux(newValue as Boolean)
true
@@ -171,6 +183,16 @@ class SettingsActivity : BaseActivity() {
mode?.dialogLayoutResource = R.layout.preference_with_help_link
//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() {
@@ -181,6 +203,7 @@ class SettingsActivity : BaseActivity() {
appendHttpProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY, false)
localDnsPort?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
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))
mux?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
@@ -204,6 +227,9 @@ class SettingsActivity : BaseActivity() {
dnsHosts?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
delayTestUrl?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL, AppConfig.DELAY_TEST_URL)
updateHevTunSettings(MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL, true))
hevTunRwTimeout?.summary = MmkvManager.decodeSettingsString(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT, AppConfig.HEVTUN_RW_TIMEOUT)
initSharedPreference()
}
@@ -211,6 +237,7 @@ class SettingsActivity : BaseActivity() {
listOf(
localDnsPort,
vpnDns,
vpnMtu,
muxConcurrency,
muxXudpConcurrency,
fragmentLength,
@@ -219,13 +246,15 @@ class SettingsActivity : BaseActivity() {
socksPort,
remoteDns,
domesticDns,
delayTestUrl
delayTestUrl,
hevTunRwTimeout
).forEach { key ->
key?.text = key?.summary.toString()
}
listOf(
AppConfig.PREF_SNIFFING_ENABLED,
AppConfig.PREF_USE_HEV_TUNNEL
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
MmkvManager.decodeSettingsBool(key, true)
@@ -249,13 +278,17 @@ class SettingsActivity : BaseActivity() {
listOf(
AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_FRAGMENT_PACKETS,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
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 ->
if (MmkvManager.decodeSettingsString(key) != null) {
findPreference<ListPreference>(key)?.value = MmkvManager.decodeSettingsString(key)
@@ -265,15 +298,16 @@ class SettingsActivity : BaseActivity() {
private fun updateMode(mode: String?) {
val vpn = mode == VPN
perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
// perAppProxy?.isEnabled = vpn
// perAppProxy?.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn
appendHttpProxy?.isEnabled = vpn
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
vpnBypassLan?.isEnabled = vpn
vpn
vpnInterfaceAddress?.isEnabled = vpn
vpnMtu?.isEnabled = vpn
if (vpn) {
updateLocalDns(
MmkvManager.decodeSettingsBool(
@@ -361,6 +395,11 @@ class SettingsActivity : BaseActivity() {
private fun updateFragmentInterval(value: String?) {
fragmentInterval?.summary = value.toString()
}
private fun updateHevTunSettings(enabled: Boolean) {
hevTunLogLevel?.isEnabled = enabled
hevTunRwTimeout?.isEnabled = enabled
}
}
fun onModeHelpClicked(view: View) {
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem
@@ -43,7 +44,9 @@ class SubEditActivity : BaseActivity() {
private fun bindingServer(subItem: SubscriptionItem): Boolean {
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url)
binding.etUserAgent.text = Utils.getEditable(subItem.userAgent)
binding.etFilter.text = Utils.getEditable(subItem.filter)
binding.etIntelligentSelectionFilter.text = Utils.getEditable(subItem.intelligentSelectionFilter)
binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl
@@ -59,6 +62,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = null
binding.etUrl.text = null
binding.etFilter.text = null
binding.etIntelligentSelectionFilter.text = null
binding.chkEnable.isChecked = true
binding.etPreProfile.text = null
binding.etNextProfile.text = null
@@ -73,7 +77,9 @@ class SubEditActivity : BaseActivity() {
subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString()
subItem.userAgent = binding.etUserAgent.text.toString()
subItem.filter = binding.etFilter.text.toString()
subItem.intelligentSelectionFilter = binding.etIntelligentSelectionFilter.text.toString()
subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
subItem.prevProfile = binding.etPreProfile.text.toString()
@@ -109,19 +115,28 @@ class SubEditActivity : BaseActivity() {
*/
private fun deleteServer(): Boolean {
if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
launch(Dispatchers.Main) {
finish()
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
MmkvManager.removeSubscription(editSubId)
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
}
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AppConfig
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.util.QRCodeDecoder
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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 ->
if (!it.isPressed) return@setOnCheckedChangeListener
subItem.enabled = isChecked
@@ -54,9 +61,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
}
if (TextUtils.isEmpty(subItem.url)) {
holder.itemSubSettingBinding.layoutUrl.visibility = View.GONE
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE
} else {
holder.itemSubSettingBinding.layoutUrl.visibility = View.VISIBLE
holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE
holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE
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 {
return MainViewHolder(
ItemRecyclerSubSettingBinding.inflate(
@@ -12,18 +12,22 @@ import java.net.IDN
import java.net.Inet6Address
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.MalformedURLException
import java.net.Proxy
import java.net.URI
import java.net.URL
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.
* @return The ASCII representation of the URL.
* For example, a URL like "https://例子.中国/path" will be converted to "https://xn--fsqu00a.xn--fiqs8s/path".
*
* @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 host = url.host
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
*
@@ -102,7 +128,7 @@ object HttpUtil {
* @throws IOException If an I/O error occurs.
*/
@Throws(IOException::class)
fun getUrlContentWithUserAgent(url: String?, timeout: Int = 15000, httpPort: Int = 0): String {
fun getUrlContentWithUserAgent(url: String?, userAgent: String?, timeout: Int = 15000, httpPort: Int = 0): String {
var currentUrl = url
var redirects = 0
val maxRedirects = 3
@@ -110,13 +136,18 @@ object HttpUtil {
while (redirects++ < maxRedirects) {
if (currentUrl == null) continue
val conn = createProxyConnection(currentUrl, httpPort, timeout, timeout) ?: continue
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
val finalUserAgent = if (userAgent.isNullOrBlank()) {
"v2rayNG/${BuildConfig.VERSION_NAME}"
} else {
userAgent
}
conn.setRequestProperty("User-agent", finalUserAgent)
conn.connect()
val responseCode = conn.responseCode
when (responseCode) {
in 300..399 -> {
val location = conn.getHeaderField("Location")
val location = resolveLocation(conn)
conn.disconnect()
if (location.isNullOrEmpty()) {
throw IOException("Redirect location not found")
@@ -195,5 +226,29 @@ object HttpUtil {
}
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
}
}
}
}
}
@@ -134,11 +134,16 @@ object Utils {
* Encode a string to base64.
*
* @param text The string to encode.
* @param removePadding
* @return The base64 encoded string, or an empty string if encoding fails.
*/
fun encode(text: String): String {
fun encode(text: String, removePadding : Boolean = false): String {
return try {
Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
var encoded = Base64.encodeToString(text.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
if (removePadding) {
encoded = encoded.trimEnd('=')
}
encoded
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to encode text to base64", e)
""
@@ -198,6 +203,21 @@ object Utils {
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.
*
@@ -384,6 +384,29 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
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.
* @param assets The asset manager.
@@ -41,6 +41,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS,
AppConfig.PREF_VPN_BYPASS_LAN,
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
AppConfig.PREF_VPN_MTU,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_DNS_HOSTS,
@@ -48,6 +50,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_LOCAL_DNS_PORT,
AppConfig.PREF_SOCKS_PORT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_INTELLIGENT_SELECTION_METHOD,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
@@ -56,6 +60,8 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_FRAGMENT_LENGTH,
AppConfig.PREF_FRAGMENT_INTERVAL,
AppConfig.PREF_MUX_XUDP_QUIC,
AppConfig.PREF_HEV_TUNNEL_LOGLEVEL,
AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT
-> {
MmkvManager.encodeSettings(key, sharedPreferences.getString(key, ""))
}
@@ -69,19 +75,20 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_APPEND_HTTP_PROXY,
AppConfig.PREF_ALLOW_INSECURE,
AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY,
// AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_CONFIRM_REMOVE,
AppConfig.PREF_START_SCAN_IMMEDIATE,
AppConfig.PREF_DOUBLE_COLUMN_DISPLAY,
AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_FRAGMENT_ENABLED,
AppConfig.PREF_MUX_ENABLED,
AppConfig.PREF_MUX_ENABLED
-> {
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, false))
}
AppConfig.PREF_SNIFFING_ENABLED -> {
AppConfig.PREF_SNIFFING_ENABLED,
AppConfig.PREF_USE_HEV_TUNNEL -> {
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, true))
}
@@ -111,49 +111,6 @@
android:orientation="vertical"
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
android:id="@+id/layout_soure_ccode"
android:layout_width="match_parent"
@@ -279,7 +236,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp16">
<TextView
@@ -288,6 +245,13 @@
android:layout_height="wrap_content"
android:text="@string/title_about"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/tv_app_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_about"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</LinearLayout>
@@ -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>
@@ -103,6 +103,7 @@
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/view_height_dp36"
android:clickable="true"
android:contentDescription="@string/tasker_start_service"
android:focusable="true"
android:nextFocusLeft="@+id/layout_test"
android:src="@drawable/ic_play_24dp"
@@ -125,4 +126,3 @@
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
@@ -74,6 +74,25 @@
</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_user_agent" />
<EditText
android:id="@+id/et_user_agent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -93,6 +112,25 @@
</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
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -111,6 +111,7 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/title_configuration_share"
android:focusable="true"
android:gravity="center"
android:nextFocusLeft="@+id/info_container"
@@ -130,6 +131,7 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/menu_item_edit_config"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
@@ -148,6 +150,7 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/menu_item_del_config"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
@@ -166,6 +169,7 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/notification_action_more"
android:focusable="true"
android:gravity="center"
android:orientation="vertical"
@@ -15,97 +15,144 @@
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:nextFocusRight="@+id/layout_edit"
android:nextFocusRight="@+id/layout_share"
android:orientation="horizontal"
android:padding="@dimen/padding_spacing_dp8">
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
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">
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:layout_weight="1"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
android:paddingStart="@dimen/padding_spacing_dp8">
<ImageView
android:layout_width="@dimen/image_size_dp24"
android:layout_height="@dimen/image_size_dp24"
app:srcCompat="@drawable/ic_share_24dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:minLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</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:nextFocusLeft="@+id/info_container"
android:orientation="vertical"
android:padding="@dimen/padding_spacing_dp8">
android:orientation="horizontal">
<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
android:layout_width="wrap_content"
android:id="@+id/layout_url"
android:layout_width="match_parent"
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
android:id="@+id/chk_enable"
<LinearLayout
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_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>
@@ -178,4 +178,25 @@
android:nextFocusDown="@+id/sp_stream_fingerprint" />
</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>
@@ -35,6 +35,10 @@
android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_24dp"
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
android:id="@+id/about"
android:icon="@drawable/ic_about_24dp"
@@ -82,6 +82,10 @@
android:icon="@drawable/ic_share_24dp"
android:title="@string/title_export_all"
app:showAsAction="never" />
<item
android:id="@+id/intelligent_selection_all"
android:title="@string/title_create_intelligent_selection_all_server"
app:showAsAction="never" />
<item
android:id="@+id/ping_all"
android:title="@string/title_ping_all_server"
+30 -2
View File
@@ -23,6 +23,7 @@
<string name="title_server">ملف التكوين</string>
<string name="menu_item_add_config">إضافة تكوين</string>
<string name="menu_item_save_config">حفظ التكوين</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">حذف التكوين</string>
<string name="menu_item_import_config_qrcode">استيراد التكوين من رمز الاستجابة السريعة (QRcode)</string>
<string name="menu_item_import_config_clipboard">استيراد التكوين من الحافظة</string>
@@ -81,6 +82,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">المعرّف القصير</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_reserved">محجوز (اختياري)</string>
<string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string>
@@ -106,8 +108,8 @@
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
<string name="server_lab_bandwidth_down">Bandwidth down (Supported units: k/m/g/t)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (Supported units: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
@@ -141,6 +143,7 @@
<!-- Preferences -->
<string name="title_settings">الإعدادات</string>
<string name="title_advanced">إعدادات متقدمة</string>
<string name="title_core_settings">إعدادات النواة</string>
<string name="title_vpn_settings">إعدادات VPN</string>
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
@@ -181,6 +184,9 @@
<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_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="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +244,16 @@
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</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_help">انقر هنا للحصول على مزيد من المساعدة</string>
<string name="title_language">اللغة</string>
<string name="title_ui_settings">إعدادات واجهة المستخدم</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 (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">نسخ</string>
@@ -255,6 +266,7 @@
<string name="title_sub_setting">إعداد مجموعة الاشتراك</string>
<string name="sub_setting_remarks">ملاحظات</string>
<string name="sub_setting_url">عنوان URL اختياري</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">تفعيل التحديث</string>
<string name="sub_auto_update">تفعيل التحديث التلقائي</string>
@@ -265,6 +277,7 @@
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
<string name="title_ping_all_server">Tcping لجميع الإعدادات</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_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
<string name="title_filter_config">تصفية ملف التكوين</string>
@@ -316,6 +329,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</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">
<item>رمز استجابة سريعة (QRcode)</item>
@@ -353,4 +367,18 @@
<item>Not Bypass</item>
</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>
+30 -2
View File
@@ -23,6 +23,7 @@
<string name="title_server">কনফিগারেশন ফাইল</string>
<string name="menu_item_add_config">কনফিগারেশন যোগ করুন</string>
<string name="menu_item_save_config">কনফিগারেশন সংরক্ষণ করুন</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">কনফিগারেশন মুছুন</string>
<string name="menu_item_import_config_qrcode">QR কোড থেকে কনফিগারেশন আমদানি করুন</string>
<string name="menu_item_import_config_clipboard">ক্লিপবোর্ড থেকে কনফিগারেশন আমদানি করুন</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">শর্ট আইডি</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_reserved">সংরক্ষিত (ঐচ্ছিক)</string>
<string name="server_lab_local_address">স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা)</string>
@@ -105,8 +107,8 @@
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
<string name="server_lab_bandwidth_down">Bandwidth down (Supported units: k/m/g/t)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (Supported units: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
@@ -139,6 +141,7 @@
<!-- Preferences -->
<string name="title_settings">সেটিংস</string>
<string name="title_advanced">এডভান্সড সেটিংস</string>
<string name="title_core_settings">কোর সেটিংস</string>
<string name="title_vpn_settings">VPN সেটিংস</string>
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
@@ -181,6 +184,9 @@
<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_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="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +244,16 @@
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</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_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
<string name="title_language">ভাষা</string>
<string name="title_ui_settings">ইউআই সেটিংস</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 (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">লগক্যাট</string>
<string name="logcat_copy">কপি করুন</string>
@@ -255,6 +266,7 @@
<string name="title_sub_setting">সাবস্ক্রিপশন গ্রুপ সেটিং</string>
<string name="sub_setting_remarks">মন্তব্য</string>
<string name="sub_setting_url">ঐচ্ছিক URL</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">আপডেট সক্রিয় করুন</string>
<string name="sub_auto_update">স্বয়ংক্রিয় আপডেট সক্রিয় করুন</string>
@@ -265,6 +277,7 @@
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</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_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string>
<string name="title_filter_config">কনফিগারেশন ফাইল ফিল্টার করুন</string>
@@ -315,6 +328,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</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">
<item>QR কোড</item>
@@ -358,4 +372,18 @@
<item>Not Bypass</item>
</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>
@@ -23,6 +23,7 @@
<string name="title_server">فایل کانفیگ</string>
<string name="menu_item_add_config">ٱووردن کانفیگ</string>
<string name="menu_item_save_config">زفت کردن کانفیگ</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">پاک کردن کانفیگ</string>
<string name="menu_item_import_config_qrcode">و من ٱووردن کانفیگ ز QRcode</string>
<string name="menu_item_import_config_clipboard">و من ٱووردن کانفیگ ز کلیپ بورد</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string>
<string name="server_lab_short_id">ShortID</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_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
@@ -140,6 +142,7 @@
<!-- Preferences -->
<string name="title_settings">سامووا</string>
<string name="title_advanced">سامووا پؽش رئڌه</string>
<string name="title_core_settings">سامووا هسته</string>
<string name="title_vpn_settings">سامووا VPN</string>
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string>
@@ -148,7 +151,7 @@
<string name="title_mux_settings">سامووا 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_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string>
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string>
@@ -158,21 +161,21 @@
<item>گوم زیڌن</item>
</string-array>
<string name="title_pref_speed_enabled">ر وندن نشۉݩ داڌن سورعت</string>
<string name="summary_pref_speed_enabled">نشۉݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</string>
<string name="title_pref_speed_enabled">ر وندن نشووݩ داڌن سورعت</string>
<string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</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="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="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="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
<string name="title_pref_prefer_ipv6">ترجی داڌن IPv6</string>
<string name="summary_pref_prefer_ipv6">تورا IPv6 ن فعال کۊنین وو نشۊویا IPv6 ن ترجی بڌین</string>
<string name="title_pref_remote_dns">DNS ز ر دیر (اختیاری) (udp/tcp/https/quic) (اختیاری)</string>
@@ -181,6 +184,9 @@
<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_interface_address">نشۊوی رابت VPN</string>
<string name="title_pref_vpn_mtu">VPN MTU (پؽش فرز 1500)</string>
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@@ -204,21 +210,21 @@
<string name="summary_pref_local_dns_port">پورت DNS مهلی</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="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="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن وا برنومه ن ز نۊ ر ونین.</string>
<string name="title_pref_double_column_display">ر وندن نشووݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسوو موشکلا من Github</string>
<string name="summary_pref_tg_group">ٱووڌن من جرگه تلگرام</string>
<string name="summary_pref_feedback">فشناڌن منشڌ یا گوزارش موشکلا من Github</string>
<string name="summary_pref_tg_group">ٱووڌن من بونکۊ تلگرام</string>
<string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string>
<string name="title_privacy_policy">هریم سیخومی</string>
<string name="title_about">زبار</string>
@@ -237,24 +243,30 @@
<string name="summary_pref_auto_update_subscription">اشتراکا خوتۉ ن و تۉر خوتکار وا فاسله زمۊوی من پس زمینه ورۊ کۊنین. ای ویژیی من پوی دسگایل گاشڌ همیشه کار نکونه</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_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
<string name="title_language">زۉݩ</string>
<string name="title_language">زووݩ</string>
<string name="title_ui_settings">سامووا رابت منتوری</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 read/write timeout (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">داسووا</string>
<string name="title_logcat">گوزارشا</string>
<string name="logcat_copy">لف گیری</string>
<string name="logcat_clear">روفتن</string>
<string name="title_service_restart">ر وندن دووارته خدمات</string>
<string name="title_del_all_config">پاک کردن پوی کانفیگا جرگه سکویی</string>
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری جرگه سکویی</string>
<string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر جرگه سکویی</string>
<string name="title_export_all">و در کشیڌن کانفیگا غیر سفارشی جرگه سکویی من کلیپ بورد</string>
<string name="title_sub_setting">سامووا جرگه اشتراک</string>
<string name="title_del_all_config">پاک کردن پوی کانفیگا بونکۊ سکویی</string>
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری بونکۊ سکویی</string>
<string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر بونکۊ سکویی</string>
<string name="title_export_all">و در کشیڌن کانفیگا غیر سفارشی بونکۊ سکویی من کلیپ بورد</string>
<string name="title_sub_setting">سامووا بونکۊ اشتراک</string>
<string name="sub_setting_remarks">نیشتنا</string>
<string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">نوم موستعار فیلتر</string>
<string name="sub_setting_enable">فعال بیڌن ورۊ کردن</string>
<string name="sub_auto_update">فعال بیڌن ورۊ کردن خوتکار</string>
@@ -262,13 +274,14 @@
<string name="sub_setting_pre_profile">نوم موستعار پروکسی دیندایی</string>
<string name="sub_setting_next_profile">نوم موستعار پروکسی نیایی</string>
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string>
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string>
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string>
<string name="title_sub_update">ورۊ کردن اشتراک بونکۊ سکویی</string>
<string name="title_ping_all_server">Tcping کانفیگا بونکۊ سکویی</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_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</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_config_count">پاک کردن %d کانفیگ</string>
@@ -324,6 +337,7 @@
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
<string name="update_check_pre_release">واجۊری نوسخه یل پؽش ز تیجنیڌن</string>
<string name="update_checking_for_update">ورۊ رسۊوی ن هونی واجۊری اکونه...</string>
<string-array name="share_method">
<item>QRcode</item>
@@ -368,4 +382,18 @@
<item>دور زیڌه نبۊ</item>
</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>
@@ -23,6 +23,7 @@
<string name="title_server">فایل کانفیگ</string>
<string name="menu_item_add_config">افزودن کانفیگ</string>
<string name="menu_item_save_config">ذخیره کانفیگ</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">حذف کانفیگ</string>
<string name="menu_item_import_config_qrcode">کانفیگ را از QRcode وارد کنید</string>
<string name="menu_item_import_config_clipboard">کانفیگ را از کلیپ ‌بورد وارد کنید</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
<string name="server_lab_short_id">ShortID</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_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
<string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string>
@@ -137,6 +139,7 @@
<!-- Preferences -->
<string name="title_settings">تنظیمات</string>
<string name="title_advanced">تنظیمات پیشرفته</string>
<string name="title_core_settings">تنظیمات هسته</string>
<string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string>
@@ -179,6 +182,9 @@
<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_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="summary_pref_domestic_dns">DNS</string>
@@ -235,11 +241,16 @@
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
<string name="title_pref_auto_update_interval">فاصله به‌ روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</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_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
<string name="title_language">زبان</string>
<string name="title_ui_settings">تنظیمات رابط کاربری</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 (ثانیه) (پیش‌فرض tcp,udp 300،60)</string>
<string name="title_logcat">گزارشات</string>
<string name="logcat_copy">کپی</string>
@@ -252,6 +263,7 @@
<string name="title_sub_setting">تنظیمات گروه‌ اشتراک</string>
<string name="sub_setting_remarks">ملاحظات</string>
<string name="sub_setting_url">نشانی اینترنتی اختیاری</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">نام مستعار فیلتر</string>
<string name="sub_setting_enable">فعال کردن به‌روزرسانی</string>
<string name="sub_auto_update">فعال سازی به‌روزرسانی خودکار</string>
@@ -262,6 +274,7 @@
<string name="title_sub_update">به‌روزرسانی گروه فعلی اشتراک</string>
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</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_sort_by_test_results">مرتب‌ سازی بر اساس نتایج آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگ‌ها</string>
@@ -321,6 +334,7 @@
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
<string name="update_now">اکنون به روز رسانی کنید</string>
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
<string name="update_checking_for_update">در حال بررسی برای به‌روزرسانی…</string>
<string-array name="share_method">
<item>QRcode</item>
@@ -367,4 +381,18 @@
<item>دور زده نشود</item>
</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>
+52 -24
View File
@@ -23,6 +23,7 @@
<string name="title_server">Профиль</string>
<string name="menu_item_add_config">Добавить профиль</string>
<string name="menu_item_save_config">Сохранить профиль</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">Удалить профиль</string>
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">Дополнительный ключ шифрования (необязательно)</string>
<string name="server_lab_short_id">ShortID</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_reserved">Reserved (необязательно, через запятую)</string>
<string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string>
@@ -89,7 +91,7 @@
<string name="toast_none_data">Ничего нет</string>
<string name="toast_incorrect_protocol">Неправильный протокол</string>
<string name="toast_decoding_failed">Невозможно декодировать</string>
<string name="title_file_chooser">Выберите файл профиля</string>
<string name="title_file_chooser">Выберите профиль</string>
<string name="toast_require_file_manager">Установите файловый менеджер</string>
<string name="server_customize_config">Изменить профиль</string>
<string name="toast_config_file_invalid">Неправильный профиль</string>
@@ -105,8 +107,8 @@
<string name="server_lab_port_hop">Смена портов (переопределяет порт)</string>
<string name="server_lab_port_hop_interval">Интервал смены портов</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_bandwidth_down">Входящая пропускная способность (единицы)</string>
<string name="server_lab_bandwidth_up">Исходящая пропускная способность (единицы)</string>
<string name="server_lab_bandwidth_down">Входящая пропускная способность (допускаются: k/m/g/t)</string>
<string name="server_lab_bandwidth_up">Исходящая пропускная способность (допускаются: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">Режим XHTTP</string>
<string name="server_lab_xhttp_extra">Необработанный JSON XHTTP Extra, формат: { XHTTPObject }</string>
@@ -139,6 +141,7 @@
<!-- Preferences -->
<string name="title_settings">Настройки</string>
<string name="title_advanced">Расширенные настройки</string>
<string name="title_core_settings">Настройки ядра</string>
<string name="title_vpn_settings">Настройки VPN</string>
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string>
<string name="summary_pref_per_app_proxy">Основной: выбранное приложение соединяется через прокси, не выбранное — напрямую;\nРежим обхода: выбранное приложение соединяется напрямую, не выбранное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string>
@@ -152,16 +155,16 @@
<string name="title_pref_mux_xudp_concurency">XUDP-соединения (диапазон от 1 до 1024)</string>
<string name="title_pref_mux_xudp_quic">Обработка QUIC в мультиплексном туннеле</string>
<string-array name="mux_xudp_quic_entries">
<item>отклонять</item>
<item>разрешать</item>
<item>пропускать</item>
<item>Отклонять</item>
<item>Разрешать</item>
<item>Пропускать</item>
</string-array>
<string name="title_pref_speed_enabled">Отображение скорости</string>
<string name="title_pref_speed_enabled">Показывать скорость</string>
<string name="summary_pref_speed_enabled">Показывать текущую скорость в уведомлении.\nЗначок будет меняться в зависимости от использования.</string>
<string name="title_pref_sniffing_enabled">Анализ пакетов</string>
<string name="summary_pref_sniffing_enabled">Использовать определение доменных имён в пакетах (по умолчанию включено)</string>
<string name="title_pref_sniffing_enabled">Анализировать пакеты</string>
<string name="summary_pref_sniffing_enabled">Пытаться определелять доменные имена в пакетах (по умолчанию включено)</string>
<string name="title_pref_route_only_enabled">Домен только для маршрутизации</string>
<string name="summary_pref_route_only_enabled">Использовать доменное имя только для маршрутизации и сохранять целевой адрес в виде IP.</string>
@@ -178,7 +181,10 @@
<string name="summary_pref_remote_dns">DNS</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="summary_pref_domestic_dns">DNS</string>
@@ -186,14 +192,14 @@
<string name="title_pref_dns_hosts">Узлы DNS (формат: домен:адрес,…)</string>
<string name="summary_pref_dns_hosts">домен:адрес,…</string>
<string name="title_pref_delay_test_url">Сервис проверки времени отклика (HTTP/HTTPS)</string>
<string name="title_pref_delay_test_url">Сервис проверки задержки (HTTP/HTTPS)</string>
<string name="summary_pref_delay_test_url">URL</string>
<string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string>
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать локальный прокси. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</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="title_pref_socks_port">Порт локального прокси</string>
@@ -202,17 +208,17 @@
<string name="title_pref_local_dns_port">Порт локальной DNS</string>
<string name="summary_pref_local_dns_port">Порт локальной DNS</string>
<string name="title_pref_confirm_remove">Подтверждение удаления профиля</string>
<string name="summary_pref_confirm_remove">Требовать двойное подтверждение удаления профиля</string>
<string name="title_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="title_pref_append_http_proxy">Дополнительный HTTP-прокси</string>
<string name="summary_pref_append_http_proxy">HTTP-прокси будет использоваться напрямую (из браузера и других поддерживающих приложений), минуя виртуальный сетевой адаптер (Android 10+)</string>
<string name="title_pref_double_column_display">Отображение в два столбца</string>
<string name="summary_pref_double_column_display">Список профилей выводится в виде двух столбцов, что позволяет показать больше информации на экране. Требуется перезапуск приложения.</string>
<string name="title_pref_double_column_display">Профили в два столбца</string>
<string name="summary_pref_double_column_display">Список профилей отображается двумя столбцами, что позволяет показать больше информации на экране. Требуется перезапуск приложения.</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">Обратная связь</string>
@@ -232,16 +238,21 @@
<string name="title_pref_promotion">Содействие</string>
<string name="summary_pref_promotion">Содействие, нажмите для получения подробной информации (пожертвование может быть удалено)</string>
<string name="title_pref_auto_update_subscription">Автоматическое обновление подписок</string>
<string name="title_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_core_loglevel">Подробность ведения журнала</string>
<string name="title_outbound_domain_resolve_method">Предопределение исходящего домена</string>
<string name="title_mode">Режим</string>
<string name="title_mode_help">Нажмите для получения дополнительной информации</string>
<string name="title_language">Язык</string>
<string name="title_ui_settings">Настройки интерфейса</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">Hev Tun read/write timeout (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">Журнал</string>
<string name="logcat_copy">Копировать</string>
@@ -254,6 +265,7 @@
<string name="title_sub_setting">Группы</string>
<string name="sub_setting_remarks">Название</string>
<string name="sub_setting_url">URL (необязательно)</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">Название фильтра</string>
<string name="sub_setting_enable">Использовать обновление</string>
<string name="sub_auto_update">Использовать автообновление</string>
@@ -262,11 +274,12 @@
<string name="sub_setting_next_profile">Следующая конфигурация прокси</string>
<string name="sub_setting_pre_profile_tip">Конфигурация должна быть уникальной</string>
<string name="title_sub_update">Обновить подписку группы</string>
<string name="title_ping_all_server">Проверка профилей группы</string>
<string name="title_real_ping_all_server">Время отклика профилей группы</string>
<string name="title_ping_all_server">Пинг-тест профилей группы</string>
<string name="title_real_ping_all_server">URL-тест профилей группы</string>
<string name="title_create_intelligent_selection_all_server">Создать конфигурацию умного выбора</string>
<string name="title_user_asset_setting">Файлы ресурсов</string>
<string name="title_sort_by_test_results">Сортировка по результатам теста</string>
<string name="title_filter_config">Фильтр групп</string>
<string name="title_sort_by_test_results">Сортировать по результатам теста</string>
<string name="title_filter_config">Фильтр профилей</string>
<string name="filter_config_all">Все группы</string>
<string name="title_del_duplicate_config_count">Удалено дубликатов профилей: %d</string>
<string name="title_del_config_count">Удалено профилей: %d</string>
@@ -323,6 +336,7 @@
<string name="update_new_version_found">Найдена новая версия: %s</string>
<string name="update_now">Обновить</string>
<string name="update_check_pre_release">Искать предварительный выпуск</string>
<string name="update_checking_for_update">Проверка обновления…</string>
<string-array name="share_method">
<item>QR-код</item>
@@ -363,8 +377,22 @@
<string-array name="vpn_bypass_lan">
<item>Как в профиле</item>
<item>Пропускает</item>
<item>Не пропускает</item>
<item>Обходит</item>
<item>Не обходит</item>
</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>
+30 -2
View File
@@ -23,6 +23,7 @@
<string name="title_server">v2rayNG</string>
<string name="menu_item_add_config">Thêm cấu hình</string>
<string name="menu_item_save_config">Lưu cấu hình</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">Xoá cấu hình</string>
<string name="menu_item_import_config_qrcode">Nhập cấu hình từ mã QR</string>
<string name="menu_item_import_config_clipboard">Nhập cấu hình từ Clipboard</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</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_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>
@@ -105,8 +107,8 @@
<string name="server_lab_port_hop">Port Hopping</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
<string name="server_lab_bandwidth_down">Bandwidth down (Supported units: k/m/g/t)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (Supported units: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
@@ -138,6 +140,7 @@
<!-- Preferences -->
<string name="title_settings">Cài đặt</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_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>
@@ -181,6 +184,9 @@
<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_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="summary_pref_domestic_dns">DNS</string>
@@ -238,11 +244,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_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_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_ui_settings">Cài đặt 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 (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">Sao chép</string>
@@ -255,6 +266,7 @@
<string name="title_sub_setting">Các gói đăng ký</string>
<string name="sub_setting_remarks">Tên gói đăng ký</string>
<string name="sub_setting_url">URL gói đăng ký</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">Sử dụng gói đăng ký này</string>
<string name="sub_auto_update">Bật tự động cập nhật</string>
@@ -265,6 +277,7 @@
<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_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_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>
@@ -317,6 +330,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</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">
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
@@ -355,4 +369,18 @@
<item>Not Bypass</item>
</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>
@@ -3,9 +3,10 @@
<string name="app_widget_name">开关</string>
<string name="app_tile_name">开关</string>
<string name="app_tile_first_use">初次使用此功能请先用 APP 添加配置</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="navigation_drawer_open">打开导航栏</string>
<string name="navigation_drawer_close">关闭导航栏</string>
<string name="migration_success">数据迁移成功!</string>
<string name="action_stop_service">停止服务</string>
<string name="migration_fail">数据迁移失败啦!</string>
<string name="pull_down_to_refresh">请下拉刷新!</string>
@@ -20,9 +21,10 @@
<string name="toast_services_failure">启动服务失败</string>
<!--ServerActivity-->
<string name="title_server">配置文件</string>
<string name="title_server">配置</string>
<string name="menu_item_add_config">添加配置</string>
<string name="menu_item_save_config">保存配置</string>
<string name="menu_item_edit_config">编辑配置</string>
<string name="menu_item_del_config">删除配置</string>
<string name="menu_item_import_config_qrcode">扫描二维码</string>
<string name="menu_item_import_config_clipboard">从剪贴板导入</string>
@@ -80,6 +82,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</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_reserved">Reserved (可选,逗号隔开)</string>
<string name="server_lab_local_address">本地地址 (可选 IPv4/IPv6,逗号隔开)</string>
@@ -89,10 +92,10 @@
<string name="toast_none_data">没有数据</string>
<string name="toast_incorrect_protocol">不正确的协议</string>
<string name="toast_decoding_failed">解码失败</string>
<string name="title_file_chooser">选择一个配置文件</string>
<string name="title_file_chooser">选择一个配置</string>
<string name="toast_require_file_manager">请安装一个文件管理器</string>
<string name="server_customize_config">自定义配置</string>
<string name="toast_config_file_invalid">无效的配置文件</string>
<string name="toast_config_file_invalid">无效的配置</string>
<string name="server_lab_content">内容</string>
<string name="toast_none_data_clipboard">剪贴板中没有数据</string>
<string name="toast_invalid_url">无效的网址</string>
@@ -105,8 +108,8 @@
<string name="server_lab_port_hop">跳跃端口 (会覆盖服务器端口)</string>
<string name="server_lab_port_hop_interval">端口跳跃间隔 (秒)</string>
<string name="server_lab_stream_pinsha256">SHA256 证书指纹</string>
<string name="server_lab_bandwidth_down">带宽下行 (单位)</string>
<string name="server_lab_bandwidth_up">带宽上行 (单位)</string>
<string name="server_lab_bandwidth_down">带宽下行 (支持的单位 k/m/g/t)</string>
<string name="server_lab_bandwidth_up">带宽上行 (支持的单位 k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
@@ -137,6 +140,7 @@
<!-- Preferences -->
<string name="title_settings">设置</string>
<string name="title_advanced">进阶设置</string>
<string name="title_core_settings">核心设置</string>
<string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用</string>
<string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
@@ -178,6 +182,9 @@
<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_interface_address">VPN 接口地址</string>
<string name="title_pref_vpn_mtu">VPN MTU (默认 1500)</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string>
@@ -200,8 +207,8 @@
<string name="title_pref_local_dns_port">本地 DNS 端口</string>
<string name="summary_pref_local_dns_port">本地 DNS 端口</string>
<string name="title_pref_confirm_remove">删除配置文件确认</string>
<string name="summary_pref_confirm_remove">删除配置文件是否需要用户二次确认</string>
<string name="title_pref_confirm_remove">删除配置确认</string>
<string name="summary_pref_confirm_remove">删除配置是否需要用户二次确认</string>
<string name="title_pref_start_scan_immediate">立即启动扫码</string>
<string name="summary_pref_start_scan_immediate">启动时立即打开相机扫描,否则可在工具栏选择扫码或选照片</string>
@@ -210,7 +217,7 @@
<string name="summary_pref_append_http_proxy">浏览器 / 一些支持的应用 将直接使用 HTTP 代理, 而不经过虚拟网卡设备 (Android 10+)</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 -->
<string name="title_pref_feedback">反馈</string>
@@ -235,11 +242,16 @@
<string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15</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_help">点此查看更多帮助</string>
<string name="title_language">语言</string>
<string name="title_ui_settings">用户界面设置</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 读写超时(秒) (tcp,udp 默认 300,60)</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</string>
@@ -252,19 +264,21 @@
<string name="title_sub_setting">订阅分组设置</string>
<string name="sub_setting_remarks">备注</string>
<string name="sub_setting_url">可选地址 (url)</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">别名正则过滤</string>
<string name="sub_setting_enable">启用更新</string>
<string name="sub_auto_update">启用自动更新</string>
<string name="sub_allow_insecure_url">允许不安全的 HTTP 地址</string>
<string name="sub_setting_pre_profile">前置代理配置文件别名</string>
<string name="sub_setting_next_profile">落地代理配置文件別名</string>
<string name="sub_setting_pre_profile_tip">请确保配置文件别名存在并唯一</string>
<string name="sub_setting_pre_profile">前置代理配置别名</string>
<string name="sub_setting_next_profile">落地代理配置別名</string>
<string name="sub_setting_pre_profile_tip">请确保配置别名存在并唯一</string>
<string name="title_sub_update">更新当前组订阅</string>
<string name="title_ping_all_server">测试当前组配置 Tcping</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_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="title_del_duplicate_config_count">删除 %d 个重复配置</string>
@@ -315,6 +329,7 @@
<string name="update_new_version_found">发现新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">检查 Pre-release</string>
<string name="update_checking_for_update">正在检查更新中…</string>
<string-array name="share_method">
<item>二维码</item>
@@ -354,9 +369,23 @@
</string-array>
<string-array name="vpn_bypass_lan">
<item>跟随配置文件</item>
<item>跟随配置</item>
<item>绕过</item>
<item>不绕过</item>
</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>
@@ -23,6 +23,7 @@
<string name="title_server">設定檔</string>
<string name="menu_item_add_config">新增設定</string>
<string name="menu_item_save_config">儲存設定</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">刪除設定</string>
<string name="menu_item_import_config_qrcode">從 QR Code 匯入設定</string>
<string name="menu_item_import_config_clipboard">從剪貼簿匯入設定</string>
@@ -80,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</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_reserved">Reserved (可選,逗號隔開)</string>
<string name="server_lab_local_address">本機位址 (可選 IPv4/IPv6,逗號隔開)</string>
@@ -105,15 +107,15 @@
<string name="server_lab_port_hop">跳躍連接埠 (會覆蓋伺服器連接埠)</string>
<string name="server_lab_port_hop_interval">連接埠跳躍間隔 (秒)</string>
<string name="server_lab_stream_pinsha256">SHA256 憑證指紋</string>
<string name="server_lab_bandwidth_down">頻寬下行 (單位)</string>
<string name="server_lab_bandwidth_up">頻寬上行 (單位)</string>
<string name="server_lab_bandwidth_down">頻寬下行 (支持的單位 k/m/g/t)</string>
<string name="server_lab_bandwidth_up">頻寬上行 (支持的單位 k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP 模式</string>
<string name="server_lab_xhttp_extra">XHTTP Extra 原始 JSON,格式: { XHTTPObject }</string>
<!-- UserAssetActivity -->
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
<string name="menu_item_add_file">新增檔案</string>
<string name="menu_item_scan_qrcode">掃描 QRcode</string>
<string name="menu_item_scan_qrcode">掃描二維碼</string>
<string name="title_url">URL</string>
<string name="menu_item_download_file">下載檔案</string>
<string name="title_user_asset_add_url">新增資產網址</string>
@@ -127,17 +129,18 @@
<string name="menu_item_select_all">全選</string>
<string name="msg_enter_keywords">輸入關鍵字</string>
<string name="switch_bypass_apps_mode">略過模式</string>
<string name="menu_item_select_proxy_app">自動選中需 Proxy 應用</string>
<string name="menu_item_select_proxy_app">自動選中需代理的應用</string>
<string name="msg_downloading_content">正在下載內容</string>
<string name="menu_item_export_proxy_app">匯出至剪貼簿</string>
<string name="menu_item_import_proxy_app">從剪貼簿匯入</string>
<string name="per_app_proxy_settings">Per-app settings</string>
<string name="per_app_proxy_settings_enable">Enable per-app</string>
<string name="per_app_proxy_settings">分應用設置</string>
<string name="per_app_proxy_settings_enable">根據應用分流</string>
<!-- Preferences -->
<string name="title_settings">設定</string>
<string name="title_advanced">進階</string>
<string name="title_core_settings">核心設定</string>
<string name="title_vpn_settings">VPN 設定</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>
@@ -180,6 +183,9 @@
<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_interface_address">VPN 介面位址</string>
<string name="title_pref_vpn_mtu">VPN MTU (預設 1500)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
@@ -195,8 +201,8 @@
<string name="title_pref_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
<string name="summary_pref_allow_insecure">傳輸層安全選 tls 時,預設跳過憑證驗證 (allowInsecure)</string>
<string name="title_pref_socks_port">本地 Proxy </string>
<string name="summary_pref_socks_port">本地 Proxy </string>
<string name="title_pref_socks_port">本地代理</string>
<string name="summary_pref_socks_port">本地代理</string>
<string name="title_pref_local_dns_port">本機 DNS 埠</string>
<string name="summary_pref_local_dns_port">本機 DNS 埠</string>
@@ -236,11 +242,16 @@
<string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15</string>
<string name="title_core_loglevel">記錄層級</string>
<string name="title_outbound_domain_resolve_method">出口網域預解析方式</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">輕觸以檢視說明</string>
<string name="title_language">語言</string>
<string name="title_ui_settings">介面顏色設定</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 讀寫逾時(秒) (tcp,udp 預設 300,60)</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</string>
@@ -253,6 +264,7 @@
<string name="title_sub_setting">訂閱分組設定</string>
<string name="sub_setting_remarks">備註</string>
<string name="sub_setting_url">可選位址(url)</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">別名正規過濾</string>
<string name="sub_setting_enable">啟用更新</string>
<string name="sub_auto_update">啟用自動更新</string>
@@ -263,6 +275,7 @@
<string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組設定 Tcping</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_sort_by_test_results">依偵測結果排序</string>
<string name="title_filter_config">過濾設定</string>
@@ -286,7 +299,7 @@
<string name="routing_settings_import_predefined_rulesets">匯入預先定義的規則集</string>
<string name="routing_settings_import_rulesets_tip">將刪除現有的規則集,是否確定繼續? </string>
<string name="routing_settings_import_rulesets_from_clipboard">從剪貼簿匯入規則集</string>
<string name="routing_settings_import_rulesets_from_qrcode"> QRcode 匯入規則集</string>
<string name="routing_settings_import_rulesets_from_qrcode">二維碼匯入規則集</string>
<string name="routing_settings_export_rulesets_to_clipboard">匯出規則集至剪貼簿</string>
<string name="routing_settings_locked">鎖定中,匯入預設時不刪除此規則</string>
@@ -315,9 +328,10 @@
<string name="update_new_version_found">發現新版本: %s</string>
<string name="update_now">立即更新</string>
<string name="update_check_pre_release">檢查 Pre-release</string>
<string name="update_checking_for_update">正在檢查更新中…</string>
<string-array name="share_method">
<item>QR Code</item>
<item>二維碼</item>
<item>匯出至剪貼簿</item>
<item>匯出完整設定至剪貼簿</item>
</string-array>
@@ -331,7 +345,7 @@
</string-array>
<string-array name="share_sub_method">
<item>QR Code</item>
<item>二維碼</item>
<item>匯出至剪貼簿</item>
</string-array>
@@ -359,4 +373,18 @@
<item>不繞過</item>
</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>
@@ -118,6 +118,13 @@
<item>Proxy only</item>
</string-array>
<string-array name="hev_tunnel_loglevel" translatable="false">
<item>error</item>
<item>warn</item>
<item>info</item>
<item>debug</item>
</string-array>
<string-array name="flows" translatable="false">
<item></item>
<item>xtls-rprx-vision</item>
@@ -182,4 +189,35 @@
<item>2</item>
</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>
+48 -17
View File
@@ -7,6 +7,7 @@
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">Data migration success!</string>
<string name="action_stop_service">Stop service</string>
<string name="migration_fail">Data migration failed!</string>
<string name="pull_down_to_refresh">Please pull down to refresh!</string>
@@ -21,13 +22,14 @@
<string name="toast_services_failure">Start Services Failure</string>
<!--ServerActivity-->
<string name="title_server">Configuration file</string>
<string name="menu_item_add_config">Add config</string>
<string name="menu_item_save_config">Save config</string>
<string name="menu_item_del_config">Delete config</string>
<string name="menu_item_import_config_qrcode">Import config from QRcode</string>
<string name="menu_item_import_config_clipboard">Import config from Clipboard</string>
<string name="menu_item_import_config_local">Import config from locally</string>
<string name="title_server">Configuration</string>
<string name="menu_item_add_config">Add configuration</string>
<string name="menu_item_save_config">Save configuration</string>
<string name="menu_item_edit_config">Edit configuration</string>
<string name="menu_item_del_config">Delete configuration</string>
<string name="menu_item_import_config_qrcode">Import configuration from QRcode</string>
<string name="menu_item_import_config_clipboard">Import configuration from Clipboard</string>
<string name="menu_item_import_config_local">Import configuration from locally</string>
<string name="menu_item_import_config_manually_vmess">Type manually[VMess]</string>
<string name="menu_item_import_config_manually_vless">Type manually[VLESS]</string>
<string name="menu_item_import_config_manually_ss">Type manually[Shadowsocks]</string>
@@ -81,6 +83,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</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_reserved">Reserved(Optional, separated by commas)</string>
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
@@ -90,24 +93,24 @@
<string name="toast_none_data">There is nothing</string>
<string name="toast_incorrect_protocol">Incorrect protocol</string>
<string name="toast_decoding_failed">Decoding failed</string>
<string name="title_file_chooser">Select a Config File</string>
<string name="title_file_chooser">Select a configuration</string>
<string name="toast_require_file_manager">Please install a File Manager.</string>
<string name="server_customize_config">Customize Config</string>
<string name="toast_config_file_invalid">Invalid Config</string>
<string name="server_customize_config">Customize configuration</string>
<string name="toast_config_file_invalid">Invalid configuration</string>
<string name="server_lab_content">Content</string>
<string name="toast_none_data_clipboard">There is no data in the clipboard</string>
<string name="toast_invalid_url">Invalid URL</string>
<string name="toast_insecure_url_protocol">Please do not use the insecure HTTP protocol subscription address</string>
<string name="server_lab_need_inbound">Ensure inbounds port is consistent with the settings</string>
<string name="toast_malformed_josn">Config malformed</string>
<string name="toast_malformed_josn">Configuration malformed</string>
<string name="server_lab_request_host6">Host(SNI)(Optional)</string>
<string name="toast_action_not_allowed">Action not allowed</string>
<string name="server_obfs_password">Obfs password</string>
<string name="server_lab_port_hop">Port Hopping(will override the port)</string>
<string name="server_lab_port_hop_interval">Port Hopping Interval</string>
<string name="server_lab_stream_pinsha256">pinSHA256</string>
<string name="server_lab_bandwidth_down">Bandwidth down (units)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (units)</string>
<string name="server_lab_bandwidth_down">Bandwidth down (Supported units: k/m/g/t)</string>
<string name="server_lab_bandwidth_up">Bandwidth up (Supported units: k/m/g/t)</string>
<string name="server_lab_xhttp_mode">XHTTP Mode</string>
<string name="server_lab_xhttp_extra">XHTTP Extra raw JSON, format: { XHTTPObject }</string>
@@ -140,6 +143,7 @@
<!-- Preferences -->
<string name="title_settings">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_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>
@@ -182,6 +186,11 @@
<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_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="summary_pref_domestic_dns">DNS</string>
@@ -204,8 +213,8 @@
<string name="title_pref_local_dns_port">Local DNS port</string>
<string name="summary_pref_local_dns_port">Local DNS port</string>
<string name="title_pref_confirm_remove">Delete configuration file confirmation</string>
<string name="summary_pref_confirm_remove">Whether to delete the configuration file requires a second confirmation by the user</string>
<string name="title_pref_confirm_remove">Delete configuration confirmation</string>
<string name="summary_pref_confirm_remove">Whether to delete the configuration requires a second confirmation by the user</string>
<string name="title_pref_start_scan_immediate">Start scanning immediately</string>
<string name="summary_pref_start_scan_immediate">Open the camera to scan immediately at startup, otherwise you can choose to scan the code or select a photo in the toolbar</string>
@@ -239,11 +248,16 @@
<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_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_mode">Mode</string>
<string name="title_mode_help">Click me for more help</string>
<string name="title_language">Language</string>
<string name="title_ui_settings">UI 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 (seconds) (tcp,udp default 300,60)</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">Copy</string>
@@ -256,6 +270,7 @@
<string name="title_sub_setting">Subscription group setting</string>
<string name="sub_setting_remarks">remarks</string>
<string name="sub_setting_url">Optional URL</string>
<string name="sub_setting_user_agent">User Agent</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">Enable update</string>
<string name="sub_auto_update">Enable automatic update</string>
@@ -266,9 +281,10 @@
<string name="title_sub_update">Update current group subscription</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_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</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_filter_config">Filter configuration file</string>
<string name="title_filter_config">Filter configuration</string>
<string name="filter_config_all">All groups</string>
<string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
<string name="title_del_config_count">Delete %d configurations</string>
@@ -325,6 +341,7 @@
<string name="update_new_version_found">New version found: %s</string>
<string name="update_now">Update now</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">
<item>QRcode</item>
@@ -364,9 +381,23 @@
</string-array>
<string-array name="vpn_bypass_lan">
<item>Follow config</item>
<item>Follow configuration</item>
<item>Bypass</item>
<item>Not Bypass</item>
</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>
+69 -16
View File
@@ -13,6 +13,7 @@
android:key="pref_route_only_enabled"
android:summary="@string/summary_pref_route_only_enabled"
android:title="@string/title_pref_route_only_enabled" />
<CheckBoxPreference
android:key="pref_is_booted"
android:summary="@string/summary_pref_is_booted"
@@ -20,14 +21,14 @@
<PreferenceCategory android:title="@string/title_vpn_settings">
<CheckBoxPreference
android:key="pref_prefer_ipv6"
android:summary="@string/summary_pref_prefer_ipv6"
android:title="@string/title_pref_prefer_ipv6" />
android:key="pref_prefer_ipv6"
android:summary="@string/summary_pref_prefer_ipv6"
android:title="@string/title_pref_prefer_ipv6" />
<CheckBoxPreference
android:key="pref_per_app_proxy"
android:summary="@string/summary_pref_per_app_proxy"
android:title="@string/title_pref_per_app_proxy" />
<!-- <CheckBoxPreference-->
<!-- android:key="pref_per_app_proxy"-->
<!-- android:summary="@string/summary_pref_per_app_proxy"-->
<!-- android:title="@string/title_pref_per_app_proxy" />-->
<CheckBoxPreference
android:key="pref_local_dns_enabled"
@@ -56,12 +57,45 @@
android:title="@string/title_pref_vpn_dns" />
<ListPreference
android:defaultValue="0"
android:defaultValue="1"
android:entries="@array/vpn_bypass_lan"
android:entryValues="@array/vpn_bypass_lan_value"
android:key="pref_vpn_bypass_lan"
android:summary="%s"
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:defaultValue="true"
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="warn"
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:key="pref_hev_tunnel_rw_timeout_v2"
android:summary="300,60"
android:title="@string/title_pref_hev_tunnel_rw_timeout" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_ui_settings">
@@ -171,9 +205,7 @@
android:title="@string/title_pref_auto_update_interval" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/title_advanced"
app:initialExpandedChildrenCount="0">
<PreferenceCategory android:title="@string/title_core_settings">
<CheckBoxPreference
android:defaultValue="false"
@@ -208,11 +240,6 @@
android:summary="@string/summary_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
android:defaultValue="warning"
android:entries="@array/core_loglevel"
@@ -221,6 +248,31 @@
android:summary="%s"
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
android:defaultValue="VPN"
android:entries="@array/mode_entries"
@@ -230,4 +282,5 @@
android:title="@string/title_mode" />
</PreferenceCategory>
</PreferenceScreen>
@@ -10,31 +10,31 @@ class HttpUtilTest {
fun testIdnToASCII() {
// Regular URL remains unchanged
val regularUrl = "https://example.com/path"
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
assertEquals(regularUrl, HttpUtil.toIdnUrl(regularUrl))
// Non-ASCII URL converts to ASCII (Punycode)
val nonAsciiUrl = "https://例子.测试/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
val mixedUrl = "https://例子.com/测试"
val expectedMixed = "https://xn--fsqu00a.com/测试"
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
assertEquals(expectedMixed, HttpUtil.toIdnUrl(mixedUrl))
// URL with Basic Authentication using regular domain
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
val basicAuthNonAscii = "https://user:password@例子.测试/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
val nonAsciiAuth = "https://用户:密码@example.com/path"
// Basic auth credentials should remain unchanged as they're percent-encoded separately
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
assertEquals(nonAsciiAuth, HttpUtil.toIdnUrl(nonAsciiAuth))
}
+9 -9
View File
@@ -1,17 +1,17 @@
[versions]
agp = "8.9.2"
agp = "8.13.2"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.20"
kotlin = "2.3.0"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.13.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
mmkvStatic = "1.3.12"
mmkvStatic = "1.3.14"
gson = "2.12.1"
quickieFoss = "1.14.0"
kotlinxCoroutinesAndroid = "1.10.2"
@@ -20,8 +20,8 @@ swiperefreshlayout = "1.1.0"
toasty = "1.5.2"
editorkit = "2.9.0"
core = "3.5.3"
workRuntimeKtx = "2.10.1"
lifecycleViewmodelKtx = "2.8.7"
workRuntimeKtx = "2.10.5"
lifecycleViewmodelKtx = "2.9.4"
multidex = "2.0.1"
mockitoMockitoInline = "5.2.0"
flexbox = "3.0.0"
+1 -1
View File
@@ -1,6 +1,6 @@
#Thu Nov 14 12:42:51 BDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
if [[ ! -d $NDK_HOME ]]; then
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"
exit 1
fi
TMPDIR=$(mktemp -d)
clear_tmp () {
rm -rf $TMPDIR
}
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
#build hev-socks5-tunnel
mkdir -p "$TMPDIR/jni"
pushd "$TMPDIR"
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="$TMPDIR/libs" \
NDK_OUT="$TMPDIR/obj" \
"APP_CFLAGS=-O3 -DPKGNAME=com/v2ray/ang/service" \
"APP_LDFLAGS=-Wl,--build-id=none -Wl,--hash-style=gnu" \
cp -r "$TMPDIR/libs/"* "$__dir/libs/"
popd
rm -rf $TMPDIR
+1
Submodule hev-socks5-tunnel added at 4682d7983c