Compare commits

...

49 Commits

Author SHA1 Message Date
2dust 5daef71147 up 1.9.8 2024-10-17 10:59:50 +08:00
Helium-Studio 5ffc5ec502 Clean up custom routing white (#3699)
Some IPs or domains are already included in geoip / geosite
2024-10-17 10:23:27 +08:00
2dust 35063db3e6 Improvement share uri 2024-10-17 10:21:13 +08:00
MMR 868c24bb8b Add Iran whitelist routing option (#3696)
* Add Iran whitelist routing option

* Update SettingsManager.kt

* Add files via upload

* Update custom_routing_white_iran

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-10-15 21:04:04 +08:00
2dust 7367baffb8 up 1.9.7 2024-10-09 17:39:16 +08:00
mayampi01 819ff2995a Add dns.pub to custom_routing_white (#3668) 2024-10-08 14:50:42 +08:00
2dust 3b5d04b717 Bug fix
https://github.com/2dust/v2rayNG/issues/3653
2024-10-08 10:26:22 +08:00
2dust b673cd73ac Fix Violation of Broken Functionality policy 2024-10-08 10:25:39 +08:00
MH 649c1a022b Update strings.xml (#3652)
update persian strings
2024-10-05 17:52:41 +08:00
MH 034e58bc9d Update strings.xml (#3649) 2024-10-05 09:59:13 +08:00
2dust a95f280102 up 1.9.6 2024-10-02 11:41:11 +08:00
2dust df8da05f32 Fix routing rules 2024-10-02 11:13:41 +08:00
2dust 635581719b Fix latency test 2024-10-01 11:30:55 +08:00
NagisaEfi 77d5e203e8 Update strings.xml (#3639) 2024-10-01 09:50:51 +08:00
solokot 370d002b25 Update Russian translation (#3636) 2024-10-01 09:49:38 +08:00
2dust 18f0fe47ff up 1.9.5 2024-09-30 19:22:25 +08:00
2dust cccd6139fc Adding latency test for hy2 2024-09-30 18:06:36 +08:00
2dust 1fadca8524 Add JsonUtil 2024-09-30 14:55:51 +08:00
2dust af01e2ac06 Improvement Intent.serializable 2024-09-30 14:20:45 +08:00
2dust de22e16cd4 Adjusting the default listening port for hy2 2024-09-30 13:31:10 +08:00
2dust e073b19343 Update subscriptions through the proxy first, then update them directly
https://github.com/2dust/v2rayNG/issues/3627
2024-09-30 10:26:39 +08:00
2dust a81a05cd45 Bug fix 2024-09-29 19:57:22 +08:00
2dust fc67281d2a Bug fix
https://github.com/2dust/v2rayNG/issues/3625
2024-09-29 17:31:15 +08:00
2dust ece35b9a1c Import and export ruleset via clipboard
https://github.com/2dust/v2rayCustomRoutingList/blob/master/custom_routing_rules_blacklist
https://github.com/2dust/v2rayCustomRoutingList/blob/master/custom_routing_rules_whitelist
2024-09-29 15:34:54 +08:00
2dust ed8fb7fa82 Add obfs for Hysteria2 2024-09-29 11:07:45 +08:00
2dust 3688dd4634 Hy2 Protocol Share 2024-09-28 19:43:28 +08:00
solokot 7ff445ef55 Update Russian translation (#3616) 2024-09-28 19:38:40 +08:00
2dust e4847b4c76 up 1.9.4 2024-09-28 16:19:42 +08:00
2dust 5f5d8f1d2f Fix
https://github.com/2dust/v2rayNG/pull/3609
2024-09-28 16:08:40 +08:00
Tamim Hossain a45aa489e8 Feature/add auto start (#3609)
* Update Kotlin Version In Readme.md

Update Kotlin Version In Readme.md

* Size check replaced with 'isNotEmpty()

* Fixed size check issue

* Add auto-start VPN feature

* Update SettingsActivity.kt

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2024-09-28 15:25:47 +08:00
2dust 9b86ba9e35 Add routing rule lock, keep this rule when import presets 2024-09-28 15:03:20 +08:00
aaa8806 f5b74ecd78 更新主页服务器地址ipv6隐藏效果 (#3615)
* Update MainRecyclerAdapter.kt

* Update MainRecyclerAdapter.kt
2024-09-28 14:52:03 +08:00
solokot 2d0ab355d6 Update Russian translation (#3611) 2024-09-28 14:49:19 +08:00
2dust e41235f76b Fix logcat 2024-09-27 14:23:08 +08:00
2dust 3045f7000e Optimize layout 2024-09-27 14:22:36 +08:00
2dust c0c2dfb657 Add insecure for Hysteria2 2024-09-27 14:22:02 +08:00
2dust 622cafbfd6 fix Gson fromJson
https://github.com/2dust/v2rayNG/issues/3534
2024-09-27 09:24:44 +08:00
2dust d8a1f66af9 Run hysteria using a plugin-like method 2024-09-26 17:52:22 +08:00
2dust c34cce63b0 Refactor fun name 2024-09-26 14:38:06 +08:00
2dust f8f17b5d38 Subscribe to add alias regular filter Add const LOOPBACK 2024-09-26 13:25:39 +08:00
solokot 1d3a194d89 Update Russian translation (#3600) 2024-09-25 21:22:34 +08:00
2dust 056384fdab up 1.9.3 2024-09-25 15:24:26 +08:00
2dust 7c40d074f3 Bug fix 2024-09-25 15:17:05 +08:00
HeXis-YS c61595eb0d Fix invalid VPN DNS (#3593) 2024-09-25 14:58:23 +08:00
2dust 7192c970fa Repair joinToString with space problem 2024-09-25 14:49:51 +08:00
2dust cfa709c651 Add HYSTERIA2 for v2fly 2024-09-25 14:29:50 +08:00
2dust 97c467af41 Optimize layout 2024-09-24 20:58:33 +08:00
2dust 6039426bac Process self package at VpnService using addDisallowedApplication 2024-09-24 17:37:19 +08:00
solokot 862fb90de9 Update Russian translation (#3592) 2024-09-24 17:03:36 +08:00
87 changed files with 2128 additions and 1413 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "com.v2ray.ang" applicationId = "com.v2ray.ang"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 593 versionCode = 602
versionName = "1.9.2" versionName = "1.9.8"
multiDexEnabled = true multiDexEnabled = true
splits { splits {
abi { abi {
+49 -42
View File
@@ -5,9 +5,9 @@
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true" android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true" /> android:xlargeScreens="true" />
<uses-sdk <uses-sdk
@@ -44,13 +44,13 @@
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> --> <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:name=".AngApplication" android:name=".AngApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner" android:banner="@mipmap/ic_banner"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppThemeDayNight" android:theme="@style/AppThemeDayNight"
@@ -77,57 +77,57 @@
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
</activity> </activity>
<activity <activity
android:exported="false"
android:name=".ui.ServerActivity" android:name=".ui.ServerActivity"
android:exported="false"
android:windowSoftInputMode="stateUnchanged" /> android:windowSoftInputMode="stateUnchanged" />
<activity <activity
android:exported="false"
android:name=".ui.ServerCustomConfigActivity" android:name=".ui.ServerCustomConfigActivity"
android:exported="false"
android:windowSoftInputMode="stateUnchanged" /> android:windowSoftInputMode="stateUnchanged" />
<activity <activity
android:exported="false" android:name=".ui.SettingsActivity"
android:name=".ui.SettingsActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.PerAppProxyActivity"
android:name=".ui.PerAppProxyActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.ScannerActivity"
android:name=".ui.ScannerActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.LogcatActivity"
android:name=".ui.LogcatActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.RoutingSettingActivity"
android:name=".ui.RoutingSettingActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.RoutingEditActivity"
android:name=".ui.RoutingEditActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.SubSettingActivity"
android:name=".ui.SubSettingActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.UserAssetActivity"
android:name=".ui.UserAssetActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.UserAssetUrlActivity"
android:name=".ui.UserAssetUrlActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.SubEditActivity"
android:name=".ui.SubEditActivity" /> android:exported="false" />
<activity <activity
android:exported="false" android:name=".ui.ScScannerActivity"
android:name=".ui.ScScannerActivity" /> android:exported="false" />
<activity <activity
android:exported="false"
android:name=".ui.ScSwitchActivity" android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false"
android:process=":RunSoLibV2RayDaemon" android:process=":RunSoLibV2RayDaemon"
android:theme="@style/AppTheme.NoActionBar.Translucent" /> android:theme="@style/AppTheme.NoActionBar.Translucent" />
<activity <activity
android:exported="true" android:name=".ui.UrlSchemeActivity"
android:name=".ui.UrlSchemeActivity"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -145,16 +145,16 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:exported="false" android:name=".ui.AboutActivity"
android:name=".ui.AboutActivity" /> android:exported="false" />
<service <service
android:name=".service.V2RayVpnService" android:name=".service.V2RayVpnService"
android:enabled="true" android:enabled="true"
android:exported="false" android:exported="false"
android:foregroundServiceType="specialUse"
android:label="@string/app_name" android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="specialUse"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
@@ -170,8 +170,8 @@
<service <service
android:name=".service.V2RayProxyOnlyService" android:name=".service.V2RayProxyOnlyService"
android:exported="false" android:exported="false"
android:label="@string/app_name"
android:foregroundServiceType="specialUse" android:foregroundServiceType="specialUse"
android:label="@string/app_name"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<property <property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
@@ -181,12 +181,11 @@
<service <service
android:name=".service.V2RayTestService" android:name=".service.V2RayTestService"
android:exported="false" android:exported="false"
android:process=":RunSoLibV2RayDaemon" android:process=":RunSoLibV2RayDaemon" />
/>
<receiver <receiver
android:exported="true"
android:name=".receiver.WidgetProvider" android:name=".receiver.WidgetProvider"
android:exported="true"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
@@ -197,13 +196,21 @@
<action android:name="com.v2ray.ang.action.activity" /> <action android:name="com.v2ray.ang.action.activity" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".receiver.BootReceiver"
android:exported="true"
android:label="BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service <service
android:exported="true"
android:name=".service.QSTileService" android:name=".service.QSTileService"
android:exported="true"
android:foregroundServiceType="specialUse"
android:icon="@drawable/ic_stat_name" android:icon="@drawable/ic_stat_name"
android:label="@string/app_tile_name" android:label="@string/app_tile_name"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<intent-filter> <intent-filter>
@@ -215,8 +222,8 @@
</service> </service>
<!-- =====================Tasker===================== --> <!-- =====================Tasker===================== -->
<activity <activity
android:exported="true"
android:name=".ui.TaskerActivity" android:name=".ui.TaskerActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
@@ -225,8 +232,8 @@
</activity> </activity>
<receiver <receiver
android:exported="true"
android:name=".receiver.TaskerReceiver" android:name=".receiver.TaskerReceiver"
android:exported="true"
android:process=":RunSoLibV2RayDaemon"> android:process=":RunSoLibV2RayDaemon">
<intent-filter> <intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" /> <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
@@ -27,13 +27,6 @@
"geosite:category-ads-all" "geosite:category-ads-all"
] ]
}, },
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{ {
"remarks": "绕过局域网IP", "remarks": "绕过局域网IP",
"outboundTag": "direct", "outboundTag": "direct",
@@ -41,6 +34,13 @@
"geoip:private" "geoip:private"
] ]
}, },
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{ {
"remarks": "代理GFW", "remarks": "代理GFW",
"outboundTag": "proxy", "outboundTag": "proxy",
@@ -12,13 +12,6 @@
"geosite:category-ads-all" "geosite:category-ads-all"
] ]
}, },
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{ {
"remarks": "绕过局域网IP", "remarks": "绕过局域网IP",
"outboundTag": "direct", "outboundTag": "direct",
@@ -26,6 +19,13 @@
"geoip:private" "geoip:private"
] ]
}, },
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{ {
"remarks": "最终代理", "remarks": "最终代理",
"port": "0-65535", "port": "0-65535",
@@ -1,12 +1,4 @@
[ [
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{ {
"remarks": "阻断udp443", "remarks": "阻断udp443",
"outboundTag": "block", "outboundTag": "block",
@@ -20,13 +12,6 @@
"geosite:category-ads-all" "geosite:category-ads-all"
] ]
}, },
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{ {
"remarks": "绕过局域网IP", "remarks": "绕过局域网IP",
"outboundTag": "direct", "outboundTag": "direct",
@@ -35,47 +20,38 @@
] ]
}, },
{ {
"remarks": "绕过中国域名", "remarks": "绕过局域网域名",
"outboundTag": "direct", "outboundTag": "direct",
"domain": [ "domain": [
"domain:dns.alidns.com", "geosite:private"
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
] ]
}, },
{ {
"remarks": "绕过中国IP", "remarks": "绕过中国IP",
"outboundTag": "direct", "outboundTag": "direct",
"ip": [ "ip": [
"223.5.5.5/32",
"223.6.6.6/32",
"2400:3200::1/128",
"2400:3200:baba::1/128",
"119.29.29.29/32",
"1.12.12.12/32",
"120.53.53.53/32",
"2402:4e00::/128",
"2402:4e00:1::/128",
"180.76.76.76/32",
"2400:da00::6666/128",
"114.114.114.114/32",
"114.114.115.115/32",
"180.184.1.1/32",
"180.184.2.2/32",
"101.226.4.6/32",
"218.30.118.6/32",
"123.125.81.6/32",
"140.207.198.6/32",
"geoip:cn" "geoip:cn"
] ]
}, },
{
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "Google CN",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{ {
"remarks": "最终代理", "remarks": "最终代理",
"port": "0-65535", "port": "0-65535",
"outboundTag": "proxy" "outboundTag": "proxy"
} }
] ]
@@ -0,0 +1,49 @@
[
{
"remarks": "Block udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "Block ads and trackers",
"outboundTag": "block",
"domain": [
"geosite:category-ads-all"
]
},
{
"remarks": "Direct LAN IP",
"outboundTag": "direct",
"ip": [
"geoip:private"
]
},
{
"remarks": "Direct LAN domains",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "Bypass Iran domains",
"outboundTag": "direct",
"domain": [
"domain:ir",
"geosite:category-ir"
]
},
{
"remarks": "Bypass Iran IP",
"outboundTag": "direct",
"ip": [
"geoip:ir"
]
},
{
"remarks": "Final Agent",
"port": "0-65535",
"outboundTag": "proxy"
}
]
@@ -1,6 +1,8 @@
package com.v2ray.ang package com.v2ray.ang
import android.content.Context import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
@@ -33,7 +35,6 @@ class AngApplication : MultiDexApplication() {
// if (firstRun) // if (firstRun)
// defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply() // defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
MMKV.initialize(this) MMKV.initialize(this)
Utils.setNightMode(application) Utils.setNightMode(application)
@@ -42,4 +43,10 @@ class AngApplication : MultiDexApplication() {
SettingsManager.initRoutingRulesets(this) SettingsManager.initRoutingRulesets(this)
} }
fun getPackageInfo(packageName: String) = packageManager.getPackageInfo(
packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
)!!
} }
@@ -48,12 +48,12 @@ object AppConfig {
const val PREF_ALLOW_INSECURE = "pref_allow_insecure" const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
const val PREF_SOCKS_PORT = "pref_socks_port" const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_HTTP_PORT = "pref_http_port" const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns" const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_DELAY_TEST_URL = "pref_delay_test_url" const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel" const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_MODE = "pref_mode" const val PREF_MODE = "pref_mode"
const val PREF_IS_BOOTED = "pref_is_booted"
/** Cache keys. */ /** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id" const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -105,6 +105,10 @@ object AppConfig {
const val DNS_PROXY = "1.1.1.1" const val DNS_PROXY = "1.1.1.1"
const val DNS_DIRECT = "223.5.5.5" const val DNS_DIRECT = "223.5.5.5"
const val DNS_VPN = "1.1.1.1" const val DNS_VPN = "1.1.1.1"
const val GEOSITE_PRIVATE = "geosite:private"
const val GEOSITE_CN = "geosite:cn"
const val GEOIP_PRIVATE = "geoip:private"
const val GEOIP_CN = "geoip:cn"
/** Ports and addresses for various services. */ /** Ports and addresses for various services. */
const val PORT_LOCAL_DNS = "10853" const val PORT_LOCAL_DNS = "10853"
@@ -113,6 +117,7 @@ object AppConfig {
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32" const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128" const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
const val WIREGUARD_LOCAL_MTU = "1420" const val WIREGUARD_LOCAL_MTU = "1420"
const val LOOPBACK = "127.0.0.1"
/** Message constants for communication. */ /** Message constants for communication. */
const val MSG_REGISTER_CLIENT = 1 const val MSG_REGISTER_CLIENT = 1
@@ -146,4 +151,11 @@ object AppConfig {
const val VLESS = "vless://" const val VLESS = "vless://"
const val TROJAN = "trojan://" const val TROJAN = "trojan://"
const val WIREGUARD = "wireguard://" const val WIREGUARD = "wireguard://"
const val TUIC = "tuic://"
const val HYSTERIA2 = "hysteria2://"
const val HY2 = "hy2://"
/** Give a good name to this, IDK*/
const val VPN = "VPN"
} }
@@ -0,0 +1,9 @@
package com.v2ray.ang.dto
data class ConfigResult (
var status: Boolean,
var guid: String? = null,
var content: String = "",
var domainPort: String? = null,
)
@@ -11,6 +11,8 @@ enum class EConfigType(val value: Int, val protocolScheme: String) {
VLESS(5, AppConfig.VLESS), VLESS(5, AppConfig.VLESS),
TROJAN(6, AppConfig.TROJAN), TROJAN(6, AppConfig.TROJAN),
WIREGUARD(7, AppConfig.WIREGUARD), WIREGUARD(7, AppConfig.WIREGUARD),
// TUIC(8, AppConfig.TUIC),
HYSTERIA2(9, AppConfig.HYSTERIA2),
HTTP(10, AppConfig.HTTP); HTTP(10, AppConfig.HTTP);
companion object { companion object {
@@ -0,0 +1,29 @@
package com.v2ray.ang.dto
data class Hysteria2Bean(
val server: String?,
val auth: String?,
val lazy: Boolean? = true,
val obfs: ObfsBean? = null,
val socks5: Socks5Bean? = null,
val http: Socks5Bean? = null,
val tls: TlsBean? = null,
) {
data class ObfsBean(
val type: String?,
val salamander: SalamanderBean?
) {
data class SalamanderBean(
val password: String?,
)
}
data class Socks5Bean(
val listen: String?,
)
data class TlsBean(
val sni: String?,
val insecure: Boolean?,
)
}
@@ -9,4 +9,5 @@ data class RulesetItem(
var network: String? = null, var network: String? = null,
var protocol: List<String>? = null, var protocol: List<String>? = null,
var enabled: Boolean = true, var enabled: Boolean = true,
var looked: Boolean? = false,
) )
@@ -16,7 +16,8 @@ data class ServerConfig(
companion object { companion object {
fun create(configType: EConfigType): ServerConfig { fun create(configType: EConfigType): ServerConfig {
when (configType) { when (configType) {
EConfigType.VMESS, EConfigType.VLESS -> EConfigType.VMESS,
EConfigType.VLESS ->
return ServerConfig( return ServerConfig(
configType = configType, configType = configType,
outboundBean = V2rayConfig.OutboundBean( outboundBean = V2rayConfig.OutboundBean(
@@ -38,7 +39,8 @@ data class ServerConfig(
EConfigType.SHADOWSOCKS, EConfigType.SHADOWSOCKS,
EConfigType.SOCKS, EConfigType.SOCKS,
EConfigType.HTTP, EConfigType.HTTP,
EConfigType.TROJAN -> EConfigType.TROJAN,
EConfigType.HYSTERIA2 ->
return ServerConfig( return ServerConfig(
configType = configType, configType = configType,
outboundBean = V2rayConfig.OutboundBean( outboundBean = V2rayConfig.OutboundBean(
@@ -10,5 +10,6 @@ data class SubscriptionItem(
val updateInterval: Int? = null, val updateInterval: Int? = null,
var prevProfile: String? = null, var prevProfile: String? = null,
var nextProfile: String? = null, var nextProfile: String? = null,
var filter: String? = null,
) )
@@ -104,7 +104,8 @@ data class V2rayConfig(
var secretKey: String? = null, var secretKey: String? = null,
val peers: List<WireGuardBean>? = null, val peers: List<WireGuardBean>? = null,
var reserved: List<Int>? = null, var reserved: List<Int>? = null,
var mtu: Int? = null var mtu: Int? = null,
var obfsPassword: String? = null,
) { ) {
data class VnextBean( data class VnextBean(
@@ -147,8 +148,6 @@ data class V2rayConfig(
val ivCheck: Boolean? = null, val ivCheck: Boolean? = null,
var users: List<SocksUsersBean>? = null var users: List<SocksUsersBean>? = null
) { ) {
data class SocksUsersBean( data class SocksUsersBean(
var user: String = "", var user: String = "",
var pass: String = "", var pass: String = "",
@@ -177,6 +176,7 @@ data class V2rayConfig(
var quicSettings: QuicSettingBean? = null, var quicSettings: QuicSettingBean? = null,
var realitySettings: TlsSettingsBean? = null, var realitySettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null, var grpcSettings: GrpcSettingsBean? = null,
var hy2steriaSettings: Hy2steriaSettingsBean? = null,
val dsSettings: Any? = null, val dsSettings: Any? = null,
var sockopt: SockoptBean? = null var sockopt: SockoptBean? = null
) { ) {
@@ -295,6 +295,18 @@ data class V2rayConfig(
var health_check_timeout: Int? = null var health_check_timeout: Int? = null
) )
data class Hy2steriaSettingsBean(
var password: String? = null,
var use_udp_extension: Boolean? = true,
var congestion: Hy2CongestionBean? = null
) {
data class Hy2CongestionBean(
var type: String? = "bbr",
var up_mbps: Int? = null,
var down_mbps: Int? = null,
)
}
fun populateTransportSettings( fun populateTransportSettings(
transport: String, headerType: String?, host: String?, path: String?, seed: String?, transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?, quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
@@ -427,6 +439,7 @@ data class V2rayConfig(
|| protocol.equals(EConfigType.SOCKS.name, true) || protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true) || protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true) || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) { ) {
return settings?.servers?.get(0)?.address return settings?.servers?.get(0)?.address
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) { } else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
@@ -444,6 +457,7 @@ data class V2rayConfig(
|| protocol.equals(EConfigType.SOCKS.name, true) || protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true) || protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true) || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) { ) {
return settings?.servers?.get(0)?.port return settings?.servers?.get(0)?.port
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) { } else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
@@ -465,10 +479,12 @@ data class V2rayConfig(
return settings?.vnext?.get(0)?.users?.get(0)?.id return settings?.vnext?.get(0)?.users?.get(0)?.id
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true) } else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true) || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) { ) {
return settings?.servers?.get(0)?.password return settings?.servers?.get(0)?.password
} else if (protocol.equals(EConfigType.SOCKS.name, true) } else if (protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)) { || protocol.equals(EConfigType.HTTP.name, true)
) {
return settings?.servers?.get(0)?.users?.get(0)?.pass return settings?.servers?.get(0)?.users?.get(0)?.pass
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) { } else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
return settings?.secretKey return settings?.secretKey
@@ -497,8 +513,8 @@ data class V2rayConfig(
val tcpSetting = streamSettings?.tcpSettings ?: return null val tcpSetting = streamSettings?.tcpSettings ?: return null
listOf( listOf(
tcpSetting.header.type, tcpSetting.header.type,
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(), tcpSetting.header.request?.headers?.Host?.joinToString(",").orEmpty(),
tcpSetting.header.request?.path?.joinToString().orEmpty() tcpSetting.header.request?.path?.joinToString(",").orEmpty()
) )
} }
@@ -542,7 +558,7 @@ data class V2rayConfig(
val h2Setting = streamSettings?.httpSettings ?: return null val h2Setting = streamSettings?.httpSettings ?: return null
listOf( listOf(
"", "",
h2Setting.host.joinToString(), h2Setting.host.joinToString(","),
h2Setting.path h2Setting.path
) )
} }
@@ -597,6 +613,7 @@ data class V2rayConfig(
) { ) {
data class RulesBean( data class RulesBean(
var type: String = "field",
var ip: ArrayList<String>? = null, var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null, var domain: ArrayList<String>? = null,
var outboundTag: String = "", var outboundTag: String = "",
@@ -1,11 +1,16 @@
package com.v2ray.ang.extension package com.v2ray.ang.extension
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build import android.os.Build
import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import me.drakeet.support.toast.ToastCompat import me.drakeet.support.toast.ToastCompat
import org.json.JSONObject import org.json.JSONObject
import java.io.Serializable
import java.net.URI import java.net.URI
import java.net.URLConnection import java.net.URLConnection
@@ -56,4 +61,36 @@ val URI.idnHost: String
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "") fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
fun String.toLongEx(): Long = toLongOrNull() ?: 0 fun String.toLongEx(): Long = toLongOrNull() ?: 0
fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
callback()
if (onetime) context.unregisterReceiver(this)
}
}.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(this, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package")
}, Context.RECEIVER_EXPORTED)
} else {
registerReceiver(this, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package")
})
}
}
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializable(key) as? T
}
inline fun <reified T : Serializable> Intent.serializable(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
}
@@ -0,0 +1,32 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
import android.content.pm.ResolveInfo
class NativePlugin(resolveInfo: ResolveInfo) : ResolvedPlugin(resolveInfo) {
init {
check(resolveInfo.providerInfo != null)
}
override val componentInfo get() = resolveInfo.providerInfo!!
}
@@ -0,0 +1,43 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
import android.graphics.drawable.Drawable
abstract class Plugin {
abstract val id: String
abstract val label: CharSequence
abstract val version: Int
abstract val versionName: String
open val icon: Drawable? get() = null
open val defaultConfig: String? get() = null
open val packageName: String get() = ""
open val directBootAware: Boolean get() = true
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return id == (other as Plugin).id
}
override fun hashCode() = id.hashCode()
}
@@ -0,0 +1,33 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
object PluginContract {
const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN"
const val EXTRA_ENTRY = "io.nekohasekai.sagernet.plugin.EXTRA_ENTRY"
const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id"
const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plugin.executable_path"
const val METHOD_GET_EXECUTABLE = "sagernet:getExecutable"
const val COLUMN_PATH = "path"
const val COLUMN_MODE = "mode"
const val SCHEME = "plugin"
}
@@ -0,0 +1,53 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
import android.content.Intent
import android.content.pm.PackageManager
import com.v2ray.ang.AngApplication
class PluginList : ArrayList<Plugin>() {
init {
addAll(
AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA)
.filter { it.providerInfo.exported }.map { NativePlugin(it) })
}
val lookup = mutableMapOf<String, Plugin>().apply {
for (plugin in this@PluginList.toList()) {
fun check(old: Plugin?) {
if (old != null && old != plugin) {
this@PluginList.remove(old)
}
/* if (old != null && old !== plugin) {
val packages = this@PluginList.filter { it.id == plugin.id }
.joinToString { it.packageName }
val message = "Conflicting plugins found from: $packages"
Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}*/
}
check(put(plugin.id, plugin))
}
}
}
@@ -0,0 +1,219 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-AngApplication@sekai.icu> *
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.system.Os
import android.widget.Toast
import androidx.core.os.bundleOf
import com.v2ray.ang.AngApplication
import com.v2ray.ang.extension.listenForPackageChanges
import com.v2ray.ang.plugin.PluginContract.METADATA_KEY_ID
import java.io.File
import java.io.FileNotFoundException
object PluginManager {
class PluginNotFoundException(val plugin: String) : FileNotFoundException(plugin)
private var receiver: BroadcastReceiver? = null
private var cachedPlugins: PluginList? = null
fun fetchPlugins() = synchronized(this) {
if (receiver == null) receiver = AngApplication.application.listenForPackageChanges {
synchronized(this) {
receiver = null
cachedPlugins = null
}
}
if (cachedPlugins == null) cachedPlugins = PluginList()
cachedPlugins!!
}
private fun buildUri(id: String, authority: String) = Uri.Builder()
.scheme(PluginContract.SCHEME)
.authority(authority)
.path("/$id")
.build()
data class InitResult(
val path: String,
)
@Throws(Throwable::class)
fun init(pluginId: String): InitResult? {
if (pluginId.isEmpty()) return null
var throwable: Throwable? = null
try {
val result = initNative(pluginId)
if (result != null) return result
} catch (t: Throwable) {
if (throwable == null) throwable = t //Logs.w(t)
}
throw throwable ?: PluginNotFoundException(pluginId)
}
private fun initNative(pluginId: String): InitResult? {
var flags = PackageManager.GET_META_DATA
if (Build.VERSION.SDK_INT >= 24) {
flags =
flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE
}
var providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "com.github.dyhkwong.AngApplication")), flags)
.filter { it.providerInfo.exported }
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "io.nekohasekai.AngApplication")), flags)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "moe.matsuri.lite")), flags)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(pluginId, "fr.husi")), flags)
.filter { it.providerInfo.exported }
}
if (providers.isEmpty()) {
providers = AngApplication.application.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA
).filter {
it.providerInfo.exported &&
it.providerInfo.metaData.containsKey(METADATA_KEY_ID) &&
it.providerInfo.metaData.getString(METADATA_KEY_ID) == pluginId
}
if (providers.size > 1) {
providers = listOf(providers[0]) // What if there is more than one?
}
}
if (providers.isEmpty()) return null
if (providers.size > 1) {
val message =
"Conflicting plugins found from: ${providers.joinToString { it.providerInfo.packageName }}"
Toast.makeText(AngApplication.application, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}
val provider = providers.single().providerInfo
var failure: Throwable? = null
try {
initNativeFaster(provider)?.also { return InitResult(it) }
} catch (t: Throwable) {
// Logs.w("Initializing native plugin faster mode failed")
failure = t
}
val uri = Uri.Builder().apply {
scheme(ContentResolver.SCHEME_CONTENT)
authority(provider.authority)
}.build()
try {
return initNativeFast(AngApplication.application.contentResolver,
pluginId,
uri)?.let { InitResult(it) }
} catch (t: Throwable) {
// Logs.w("Initializing native plugin fast mode failed")
failure?.also { t.addSuppressed(it) }
failure = t
}
try {
return initNativeSlow(AngApplication.application.contentResolver,
pluginId,
uri)?.let { InitResult(it) }
} catch (t: Throwable) {
failure?.also { t.addSuppressed(it) }
throw t
}
}
private fun initNativeFaster(provider: ProviderInfo): String? {
return provider.loadString(PluginContract.METADATA_KEY_EXECUTABLE_PATH)
?.let { relativePath ->
File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {
check(canExecute())
}.absolutePath
}
}
private fun initNativeFast(cr: ContentResolver, pluginId: String, uri: Uri): String? {
return cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null, bundleOf())
?.getString(PluginContract.EXTRA_ENTRY)?.also {
check(File(it).canExecute())
}
}
@SuppressLint("Recycle")
private fun initNativeSlow(cr: ContentResolver, pluginId: String, uri: Uri): String? {
var initialized = false
fun entryNotFound(): Nothing =
throw IndexOutOfBoundsException("Plugin entry binary not found")
val pluginDir = File(AngApplication.application.noBackupFilesDir, "plugin")
(cr.query(uri,
arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE),
null,
null,
null)
?: return null).use { cursor ->
if (!cursor.moveToFirst()) entryNotFound()
pluginDir.deleteRecursively()
if (!pluginDir.mkdirs()) throw FileNotFoundException("Unable to create plugin directory")
val pluginDirPath = pluginDir.absolutePath + '/'
do {
val path = cursor.getString(0)
val file = File(pluginDir, path)
check(file.absolutePath.startsWith(pluginDirPath))
cr.openInputStream(uri.buildUpon().path(path).build())!!.use { inStream ->
file.outputStream().use { outStream -> inStream.copyTo(outStream) }
}
Os.chmod(file.absolutePath, when (cursor.getType(1)) {
Cursor.FIELD_TYPE_INTEGER -> cursor.getInt(1)
Cursor.FIELD_TYPE_STRING -> cursor.getString(1).toInt(8)
else -> throw IllegalArgumentException("File mode should be of type int")
})
if (path == pluginId) initialized = true
} while (cursor.moveToNext())
}
if (!initialized) entryNotFound()
return File(pluginDir, pluginId).absolutePath
}
fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {
is String -> value
is Int -> AngApplication.application.packageManager.getResourcesForApplication(applicationInfo)
.getString(value)
null -> null
else -> error("meta-data $key has invalid type ${value.javaClass}")
}
}
@@ -0,0 +1,45 @@
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu> *
* Copyright (C) 2021 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2021 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
******************************************************************************/
package com.v2ray.ang.plugin
import android.content.pm.ComponentInfo
import android.content.pm.ResolveInfo
import android.graphics.drawable.Drawable
import android.os.Build
import com.v2ray.ang.AngApplication
import com.v2ray.ang.plugin.PluginManager.loadString
abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
protected abstract val componentInfo: ComponentInfo
override val id by lazy { componentInfo.loadString(PluginContract.METADATA_KEY_ID)!! }
override val version by lazy {
AngApplication.application.getPackageInfo(componentInfo.packageName).versionCode
}
override val versionName: String by lazy {
AngApplication.application.getPackageInfo(componentInfo.packageName).versionName!!
}
override val label: CharSequence get() = resolveInfo.loadLabel(AngApplication.application.packageManager)
override val icon: Drawable get() = resolveInfo.loadIcon(AngApplication.application.packageManager)
override val packageName: String get() = componentInfo.packageName
override val directBootAware get() = Build.VERSION.SDK_INT < 24 || componentInfo.directBootAware
}
@@ -0,0 +1,18 @@
package com.v2ray.ang.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (Intent.ACTION_BOOT_COMPLETED == intent?.action && MmkvManager.decodeStartOnBoot()) {
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
return
}
V2RayServiceManager.startV2Ray(context!!)
}
}
}
@@ -0,0 +1,45 @@
package com.v2ray.ang.service
import android.content.Context
import android.util.Log
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ProcessService {
private val TAG = ANG_PACKAGE
private lateinit var process: Process
fun runProcess(context: Context, cmd: MutableList<String>) {
Log.d(TAG, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(context.filesDir)
.start()
CoroutineScope(Dispatchers.IO).launch {
Thread.sleep(50L)
Log.d(TAG, "runProcess check")
process.waitFor()
Log.d(TAG, "runProcess exited")
}
Log.d(TAG, process.toString())
} catch (e: Exception) {
Log.d(TAG, e.toString())
}
}
fun stopProcess() {
try {
Log.d(TAG, "runProcess destroy")
process?.destroy()
} catch (e: Exception) {
Log.d(TAG, e.toString())
}
}
}
@@ -15,6 +15,7 @@ import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
@@ -40,8 +41,8 @@ object SubscriptionUpdater {
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate } val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
for (i in subs) { for (sub in subs) {
val subscription = i.second val subItem = sub.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL) notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
@@ -56,11 +57,10 @@ object SubscriptionUpdater {
notificationManager.notify(3, notification.build()) notificationManager.notify(3, notification.build())
Log.d( Log.d(
AppConfig.ANG_PACKAGE, AppConfig.ANG_PACKAGE,
"subscription automatic update: ---${subscription.remarks}" "subscription automatic update: ---${subItem.remarks}"
) )
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url) updateConfigViaSub(Pair(sub.first, subItem))
AngConfigManager.importBatchConfig(configs, i.first, false) notification.setContentText("Updating ${subItem.remarks}")
notification.setContentText("Updating ${subscription.remarks}")
} }
notificationManager.cancel(3) notificationManager.cancel(3)
return Result.success() return Result.success()
@@ -16,6 +16,7 @@ import androidx.core.app.NotificationCompat
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
@@ -24,6 +25,7 @@ import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq import go.Seq
@@ -71,7 +73,7 @@ object V2RayServiceManager {
} else { } else {
context.toast(R.string.toast_services_start) context.toast(R.string.toast_services_start)
} }
val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") { val intent = if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
Intent(context.applicationContext, V2RayVpnService::class.java) Intent(context.applicationContext, V2RayVpnService::class.java)
} else { } else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java) Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -106,13 +108,11 @@ object V2RayServiceManager {
} }
override fun onEmitStatus(l: Long, s: String?): Long { override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0 return 0
} }
override fun setup(s: String): Long { override fun setup(s: String): Long {
val serviceControl = serviceControl?.get() ?: return -1 val serviceControl = serviceControl?.get() ?: return -1
//Logger.d(s)
return try { return try {
serviceControl.startService() serviceControl.startService()
lastQueryTime = System.currentTimeMillis() lastQueryTime = System.currentTimeMillis()
@@ -163,6 +163,8 @@ object V2RayServiceManager {
if (v2rayPoint.isRunning) { if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "") MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification() showNotification()
PluginUtil.runPlugin(service, config, result.domainPort)
} else { } else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "") MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification() cancelNotification()
@@ -190,6 +192,7 @@ object V2RayServiceManager {
} catch (e: Exception) { } catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString()) Log.d(ANG_PACKAGE, e.toString())
} }
PluginUtil.stopPlugin()
} }
private class ReceiveMessageHandler : BroadcastReceiver() { private class ReceiveMessageHandler : BroadcastReceiver() {
@@ -197,7 +200,6 @@ object V2RayServiceManager {
val serviceControl = serviceControl?.get() ?: return val serviceControl = serviceControl?.get() ?: return
when (intent?.getIntExtra("key", 0)) { when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> { AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
if (v2rayPoint.isRunning) { if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "") MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
} else { } else {
@@ -6,9 +6,14 @@ import android.os.IBinder
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS 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.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.SpeedtestUtil import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq import go.Seq
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -30,10 +35,10 @@ class V2RayTestService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.getIntExtra("key", 0)) { when (intent?.getIntExtra("key", 0)) {
MSG_MEASURE_CONFIG -> { MSG_MEASURE_CONFIG -> {
val contentPair = intent.getSerializableExtra("content") as Pair<String, String> val guid = intent.serializable<String>("content") ?: ""
realTestScope.launch { realTestScope.launch {
val result = SpeedtestUtil.realPing(contentPair.second) val result = startRealPing(guid)
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result)) MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(guid, result))
} }
} }
@@ -47,4 +52,20 @@ class V2RayTestService : Service() {
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
} }
private fun startRealPing(guid: String): Long {
val retFailure = -1L
val server = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (server.getProxyOutbound()?.protocol?.equals(EConfigType.HYSTERIA2.name, true) == true) {
val delay = PluginUtil.realPingHy2(this, server)
return delay
} else {
val config = V2rayConfigUtil.getV2rayConfig(this, guid)
if (!config.status) {
return retFailure
}
return SpeedtestUtil.realPing(config.content)
}
}
} }
@@ -17,6 +17,8 @@ import android.os.StrictMode
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
@@ -137,22 +139,25 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) { // if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
builder.addDnsServer(PRIVATE_VLAN4_ROUTER) // builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
} else { // } else {
Utils.getVpnDnsServers() Utils.getVpnDnsServers()
.forEach { .forEach {
if (Utils.isPureIpAddress(it)) { if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it) builder.addDnsServer(it)
}
} }
} }
// }
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty()) builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
val selfPackageName = BuildConfig.APPLICATION_ID
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET) val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
//process self package
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
apps?.forEach { apps?.forEach {
try { try {
if (bypassApps) if (bypassApps)
@@ -160,9 +165,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
else else
builder.addAllowedApplication(it) builder.addAllowedApplication(it)
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
//Logger.d(e)
} }
} }
} else {
builder.addDisallowedApplication(selfPackageName)
} }
// Close the old interface since the parameters have been changed. // Close the old interface since the parameters have been changed.
@@ -197,12 +203,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
private fun runTun2socks() { private fun runTun2socks() {
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt()) val socksPort = SettingsManager.getSocksPort()
val cmd = arrayListOf( val cmd = arrayListOf(
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath, File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER, "--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--netif-netmask", "255.255.255.252", "--netif-netmask", "255.255.255.252",
"--socks-server-addr", "127.0.0.1:${socksPort}", "--socks-server-addr", "$LOOPBACK:${socksPort}",
"--tunmtu", VPN_MTU.toString(), "--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath, "--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
"--enable-udprelay", "--enable-udprelay",
@@ -216,7 +222,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt()) val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw") cmd.add("--dnsgw")
cmd.add("127.0.0.1:${localDnsPort}") cmd.add("$LOOPBACK:${localDnsPort}")
} }
Log.d(packageName, cmd.toString()) Log.d(packageName, cmd.toString())
@@ -29,6 +29,7 @@ import com.google.android.material.navigation.NavigationView
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.tbruyelle.rxpermissions3.RxPermissions import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityMainBinding import com.v2ray.ang.databinding.ActivityMainBinding
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
@@ -88,7 +89,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) { if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this) Utils.stopVService(this)
} else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN") == "VPN") { } else if ((settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: VPN) == VPN) {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent == null) { if (intent == null) {
startV2Ray() startV2Ray()
@@ -198,6 +199,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
fun startV2Ray() { fun startV2Ray() {
if (MmkvManager.getSelectServer().isNullOrEmpty()) { if (MmkvManager.getSelectServer().isNullOrEmpty()) {
toast(R.string.title_file_chooser)
return return
} }
V2RayServiceManager.startV2Ray(this) V2RayServiceManager.startV2Ray(this)
@@ -292,6 +294,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true true
} }
R.id.import_manually_hysteria2 -> {
importManually(EConfigType.HYSTERIA2.value)
true
}
R.id.import_config_custom_clipboard -> { R.id.import_config_custom_clipboard -> {
importConfigCustomClipboard() importConfigCustomClipboard()
true true
@@ -84,10 +84,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
} }
// 替换服务器地址的'.'之后的内容为'***',例如将'1.23.45.67'替换为'1.23.45.***' // 隐藏主页服务器地址为xxx:xxx:***/xxx.xxx.xxx.***
val modifiedServer = profile.server?.split('.')?.dropLast(1)?.joinToString(".", postfix = ".***") val strState = "${
val strState = "$modifiedServer : ${profile.serverPort}" profile.server?.let {
if (it.contains(":"))
it.split(":").take(2).joinToString(":", postfix = ":***")
else
it.split('.').dropLast(1).joinToString(".", postfix = ".***")
}
} : ${profile.serverPort}"
holder.itemMainBinding.tvStatistics.text = strState holder.itemMainBinding.tvStatistics.text = strState
@@ -2,12 +2,10 @@ package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.gson.Gson
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingEditBinding import com.v2ray.ang.databinding.ActivityRoutingEditBinding
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
@@ -40,10 +38,11 @@ class RoutingEditActivity : BaseActivity() {
private fun bindingServer(rulesetItem: RulesetItem): Boolean { private fun bindingServer(rulesetItem: RulesetItem): Boolean {
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks) binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString()) binding.chkLocked.isChecked = rulesetItem.looked ?: false
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString()) binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
binding.etPort.text = Utils.getEditable(rulesetItem.port) binding.etPort.text = Utils.getEditable(rulesetItem.port)
binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString()) binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString(","))
binding.etNetwork.text = Utils.getEditable(rulesetItem.network) binding.etNetwork.text = Utils.getEditable(rulesetItem.network)
val outbound = Utils.arrayFind(outbound_tag, rulesetItem.outboundTag) val outbound = Utils.arrayFind(outbound_tag, rulesetItem.outboundTag)
binding.spOutboundTag.setSelection(outbound) binding.spOutboundTag.setSelection(outbound)
@@ -61,9 +60,10 @@ class RoutingEditActivity : BaseActivity() {
val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem() val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem()
rulesetItem.remarks = binding.etRemarks.text.toString() rulesetItem.remarks = binding.etRemarks.text.toString()
binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(',') } rulesetItem.looked = binding.chkLocked.isChecked
binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(',') } binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(',') } binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(",").map { itt -> itt.trim() }.filter { itt -> itt.isNotEmpty() } }
binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } } binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } }
binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } } binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } }
rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition] rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition]
@@ -10,12 +10,14 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingSettingBinding import com.v2ray.ang.databinding.ActivityRoutingSettingBinding
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.SettingsManager import com.v2ray.ang.util.SettingsManager
@@ -108,6 +110,44 @@ class RoutingSettingActivity : BaseActivity() {
true true
} }
R.id.import_rulesets_from_clipboard -> {
AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip)
.setPositiveButton(android.R.string.ok) { _, _ ->
try {
val clipboard = Utils.getClipboard(this)
lifecycleScope.launch(Dispatchers.IO) {
val ret = SettingsManager.resetRoutingRulesetsFromClipboard(clipboard)
launch(Dispatchers.Main) {
if (ret) {
refreshData()
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.export_rulesets_to_clipboard -> {
val rulesetList = MmkvManager.decodeRoutingRulesets()
if (rulesetList.isNullOrEmpty()) {
toast(R.string.toast_failure)
} else {
Utils.setClipboard(this, JsonUtil.toJson(rulesetList))
toast(R.string.toast_success)
}
true
}
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
@@ -5,6 +5,7 @@ import android.graphics.Color
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.databinding.ItemRecyclerRoutingSettingBinding import com.v2ray.ang.databinding.ItemRecyclerRoutingSettingBinding
import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperAdapter
@@ -25,6 +26,7 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString() holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.looked ?: false
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener { holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener {
@@ -34,7 +36,8 @@ class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : Recy
) )
} }
holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked -> holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if( !it.isPressed) return@setOnCheckedChangeListener
ruleset.enabled = isChecked ruleset.enabled = isChecked
SettingsManager.saveRoutingRuleset(position, ruleset) SettingsManager.saveRoutingRuleset(position, ruleset)
} }
@@ -93,11 +93,11 @@ class ServerActivity : BaseActivity() {
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) } private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) } private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) }
private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) } private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) }
private val container_allow_insecure: LinearLayout? by lazy { findViewById(R.id.l5) } private val container_allow_insecure: LinearLayout? by lazy { findViewById(R.id.lay_allow_insecure) }
private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) } private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) }
private val container_sni: LinearLayout? by lazy { findViewById(R.id.l2) } private val container_sni: LinearLayout? by lazy { findViewById(R.id.lay_sni) }
private val sp_stream_fingerprint: Spinner? by lazy { findViewById(R.id.sp_stream_fingerprint) } //uTLS private val sp_stream_fingerprint: Spinner? by lazy { findViewById(R.id.sp_stream_fingerprint) } //uTLS
private val container_fingerprint: LinearLayout? by lazy { findViewById(R.id.l3) } private val container_fingerprint: LinearLayout? by lazy { findViewById(R.id.lay_stream_fingerprint) }
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) } private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) } private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) } private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
@@ -106,18 +106,19 @@ class ServerActivity : BaseActivity() {
private val tv_path: TextView? by lazy { findViewById(R.id.tv_path) } private val tv_path: TextView? by lazy { findViewById(R.id.tv_path) }
private val et_path: EditText? by lazy { findViewById(R.id.et_path) } private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS private val sp_stream_alpn: Spinner? by lazy { findViewById(R.id.sp_stream_alpn) } //uTLS
private val container_alpn: LinearLayout? by lazy { findViewById(R.id.l4) } private val container_alpn: LinearLayout? by lazy { findViewById(R.id.lay_stream_alpn) }
private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) } private val et_public_key: EditText? by lazy { findViewById(R.id.et_public_key) }
private val container_public_key: LinearLayout? by lazy { findViewById(R.id.l6) } private val container_public_key: LinearLayout? by lazy { findViewById(R.id.lay_public_key) }
private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) } private val et_short_id: EditText? by lazy { findViewById(R.id.et_short_id) }
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.l7) } private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) } private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.l8) } private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) }
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) } private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) } private val et_reserved2: EditText? by lazy { findViewById(R.id.et_reserved2) }
private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) } private val et_reserved3: EditText? by lazy { findViewById(R.id.et_reserved3) }
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) } private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) } private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
private val et_obfs_password: EditText? by lazy { findViewById(R.id.et_obfs_password) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -134,6 +135,7 @@ class ServerActivity : BaseActivity() {
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless) EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan) EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard) EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard)
EConfigType.HYSTERIA2 -> setContentView(R.layout.activity_server_hysteria2)
} }
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected( override fun onItemSelected(
@@ -284,14 +286,17 @@ class ServerActivity : BaseActivity() {
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}") Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
} else { } else {
val list = outbound.settings?.address as List<*> val list = outbound.settings?.address as List<*>
et_local_address?.text = Utils.getEditable(list.joinToString()) et_local_address?.text = Utils.getEditable(list.joinToString(","))
} }
if (outbound.settings?.mtu == null) { if (outbound.settings?.mtu == null) {
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU) et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
} else { } else {
et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString()) et_local_mtu?.text = Utils.getEditable(outbound.settings?.mtu.toString())
} }
} else if (config.configType == EConfigType.HYSTERIA2) {
et_obfs_password?.text = Utils.getEditable(outbound.settings?.obfsPassword)
} }
val securityEncryptions = val securityEncryptions =
if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security = val security =
@@ -316,7 +321,7 @@ class ServerActivity : BaseActivity() {
tlsSetting.alpn?.let { tlsSetting.alpn?.let {
val alpnIndex = Utils.arrayFind( val alpnIndex = Utils.arrayFind(
alpns, alpns,
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty() Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
) )
sp_stream_alpn?.setSelection(alpnIndex) sp_stream_alpn?.setSelection(alpnIndex)
} }
@@ -411,7 +416,10 @@ class ServerActivity : BaseActivity() {
&& config.configType != EConfigType.HTTP && config.configType != EConfigType.HTTP
&& TextUtils.isEmpty(et_id.text.toString()) && TextUtils.isEmpty(et_id.text.toString())
) { ) {
if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.SHADOWSOCKS) { if (config.configType == EConfigType.TROJAN
|| config.configType == EConfigType.SHADOWSOCKS
|| config.configType == EConfigType.HYSTERIA2
) {
toast(R.string.server_lab_id3) toast(R.string.server_lab_id3)
} else { } else {
toast(R.string.server_lab_id) toast(R.string.server_lab_id)
@@ -443,12 +451,17 @@ class ServerActivity : BaseActivity() {
wireguard?.peers?.get(0)?.let { _ -> wireguard?.peers?.get(0)?.let { _ ->
savePeer(wireguard, port) savePeer(wireguard, port)
} }
config.outboundBean?.streamSettings?.let { config.outboundBean?.streamSettings?.let {
saveStreamSettings(it) val sni = saveStreamSettings(it)
saveTls(it, sni)
} }
if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) { if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
config.subscriptionId = subscriptionId.orEmpty() config.subscriptionId = subscriptionId.orEmpty()
} }
if (config.configType == EConfigType.HYSTERIA2) {
config.outboundBean?.settings?.obfsPassword = et_obfs_password?.text?.toString()
}
MmkvManager.encodeServerConfig(editGuid, config) MmkvManager.encodeServerConfig(editGuid, config)
toast(R.string.toast_success) toast(R.string.toast_success)
@@ -493,7 +506,7 @@ class ServerActivity : BaseActivity() {
socksUsersBean.pass = et_id.text.toString().trim() socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean) server.users = listOf(socksUsersBean)
} }
} else if (config.configType == EConfigType.TROJAN) { } else if (config.configType == EConfigType.TROJAN || config.configType == EConfigType.HYSTERIA2) {
server.password = et_id.text.toString().trim() server.password = et_id.text.toString().trim()
} }
} }
@@ -515,21 +528,13 @@ class ServerActivity : BaseActivity() {
wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString()) wireguard.mtu = Utils.parseInt(et_local_mtu?.text.toString())
} }
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) { private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): String? {
val network = sp_network?.selectedItemPosition ?: return val network = sp_network?.selectedItemPosition ?: return null
val type = sp_header_type?.selectedItemPosition ?: return val type = sp_header_type?.selectedItemPosition ?: return null
val requestHost = et_request_host?.text?.toString()?.trim() ?: return val requestHost = et_request_host?.text?.toString()?.trim() ?: return null
val path = et_path?.text?.toString()?.trim() ?: return val path = et_path?.text?.toString()?.trim() ?: return null
val sniField = et_sni?.text?.toString()?.trim() ?: return
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: return
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: return
val publicKey = et_public_key?.text?.toString()?.trim() ?: return
val shortId = et_short_id?.text?.toString()?.trim() ?: return
val spiderX = et_spider_x?.text?.toString()?.trim() ?: return
var sni = streamSetting.populateTransportSettings( val sni = streamSetting.populateTransportSettings(
transport = networks[network], transport = networks[network],
headerType = transportTypes(networks[network])[type], headerType = transportTypes(networks[network])[type],
host = requestHost, host = requestHost,
@@ -541,10 +546,21 @@ class ServerActivity : BaseActivity() {
serviceName = path, serviceName = path,
authority = requestHost, authority = requestHost,
) )
if (sniField.isNotBlank()) {
sni = sniField return sni
} }
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
private fun saveTls(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, sni: String?) {
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
val sniField = et_sni?.text?.toString()?.trim()
val allowInsecureField = sp_allow_insecure?.selectedItemPosition
val utlsIndex = sp_stream_fingerprint?.selectedItemPosition ?: 0
val alpnIndex = sp_stream_alpn?.selectedItemPosition ?: 0
val publicKey = et_public_key?.text?.toString()
val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString()
val allowInsecure = if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else { } else {
allowinsecures[allowInsecureField].toBoolean() allowinsecures[allowInsecureField].toBoolean()
@@ -553,7 +569,7 @@ class ServerActivity : BaseActivity() {
streamSetting.populateTlsSettings( streamSetting.populateTlsSettings(
streamSecurity = streamSecuritys[streamSecurity], streamSecurity = streamSecuritys[streamSecurity],
allowInsecure = allowInsecure, allowInsecure = allowInsecure,
sni = sni, sni = sniField ?: sni ?: "",
fingerprint = uTlsItems[utlsIndex], fingerprint = uTlsItems[utlsIndex],
alpns = alpns[alpnIndex], alpns = alpns[alpnIndex],
publicKey = publicKey, publicKey = publicKey,
@@ -8,13 +8,14 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.editorkit.utils.EditorTheme import com.blacksquircle.ui.editorkit.utils.EditorTheme
import com.blacksquircle.ui.language.json.JsonLanguage import com.blacksquircle.ui.language.json.JsonLanguage
import com.google.gson.Gson
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import me.drakeet.support.toast.ToastCompat import me.drakeet.support.toast.ToastCompat
@@ -78,7 +79,7 @@ class ServerCustomConfigActivity : BaseActivity() {
} }
val v2rayConfig = try { val v2rayConfig = try {
Gson().fromJson(binding.editor.text.toString(), V2rayConfig::class.java) JsonUtil.fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show() ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
@@ -8,13 +8,13 @@ import androidx.activity.viewModels
import androidx.preference.CheckBoxPreference import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager import androidx.work.multiprocess.RemoteWorkManager
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toLongEx import com.v2ray.ang.extension.toLongEx
import com.v2ray.ang.service.SubscriptionUpdater import com.v2ray.ang.service.SubscriptionUpdater
@@ -172,7 +172,7 @@ class SettingsActivity : BaseActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, "VPN")) updateMode(settingsStorage.decodeString(AppConfig.PREF_MODE, VPN))
localDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false) localDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
fakeDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FAKE_DNS_ENABLED, false) fakeDns?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_FAKE_DNS_ENABLED, false)
localDnsPort?.summary = settingsStorage.decodeString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) localDnsPort?.summary = settingsStorage.decodeString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
@@ -230,6 +230,7 @@ class SettingsActivity : BaseActivity() {
listOf( listOf(
AppConfig.PREF_ROUTE_ONLY_ENABLED, AppConfig.PREF_ROUTE_ONLY_ENABLED,
AppConfig.PREF_IS_BOOTED,
AppConfig.PREF_BYPASS_APPS, AppConfig.PREF_BYPASS_APPS,
AppConfig.PREF_SPEED_ENABLED, AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_CONFIRM_REMOVE, AppConfig.PREF_CONFIRM_REMOVE,
@@ -258,7 +259,7 @@ class SettingsActivity : BaseActivity() {
} }
private fun updateMode(mode: String?) { private fun updateMode(mode: String?) {
val vpn = mode == "VPN" val vpn = mode == VPN
perAppProxy?.isEnabled = vpn perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false) perAppProxy?.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn localDns?.isEnabled = vpn
@@ -42,6 +42,7 @@ class SubEditActivity : BaseActivity() {
private fun bindingServer(subItem: SubscriptionItem): Boolean { private fun bindingServer(subItem: SubscriptionItem): Boolean {
binding.etRemarks.text = Utils.getEditable(subItem.remarks) binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url) binding.etUrl.text = Utils.getEditable(subItem.url)
binding.etFilter.text = Utils.getEditable(subItem.filter)
binding.chkEnable.isChecked = subItem.enabled binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate binding.autoUpdateCheck.isChecked = subItem.autoUpdate
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile) binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
@@ -55,6 +56,7 @@ class SubEditActivity : BaseActivity() {
private fun clearServer(): Boolean { private fun clearServer(): Boolean {
binding.etRemarks.text = null binding.etRemarks.text = null
binding.etUrl.text = null binding.etUrl.text = null
binding.etFilter.text = null
binding.chkEnable.isChecked = true binding.chkEnable.isChecked = true
binding.etPreProfile.text = null binding.etPreProfile.text = null
binding.etNextProfile.text = null binding.etNextProfile.text = null
@@ -65,10 +67,11 @@ class SubEditActivity : BaseActivity() {
* save server config * save server config
*/ */
private fun saveServer(): Boolean { private fun saveServer(): Boolean {
val subItem = MmkvManager.decodeSubscription(editSubId)?:SubscriptionItem() val subItem = MmkvManager.decodeSubscription(editSubId) ?: SubscriptionItem()
subItem.remarks = binding.etRemarks.text.toString() subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString() subItem.url = binding.etUrl.text.toString()
subItem.filter = binding.etFilter.text.toString()
subItem.enabled = binding.chkEnable.isChecked subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked subItem.autoUpdate = binding.autoUpdateCheck.isChecked
subItem.prevProfile = binding.etPreProfile.text.toString() subItem.prevProfile = binding.etPreProfile.text.toString()
@@ -44,7 +44,8 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
) )
} }
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked -> holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
if( !it.isPressed) return@setOnCheckedChangeListener
subItem.enabled = isChecked subItem.enabled = isChecked
MmkvManager.encodeSubscription(subId, subItem) MmkvManager.encodeSubscription(subId, subItem)
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.tbruyelle.rxpermissions3.RxPermissions import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubSettingBinding import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
@@ -30,6 +31,7 @@ import com.v2ray.ang.extension.toTrafficString
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -175,7 +177,7 @@ class UserAssetActivity : BaseActivity() {
.show() .show()
toast(R.string.msg_downloading_content) toast(R.string.msg_downloading_content)
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt()) val httpPort = SettingsManager.getHttpPort()
var assets = MmkvManager.decodeAssetUrls() var assets = MmkvManager.decodeAssetUrls()
assets = addBuiltInGeoItems(assets) assets = addBuiltInGeoItems(assets)
@@ -212,7 +214,7 @@ class UserAssetActivity : BaseActivity() {
URL(item.url).openConnection( URL(item.url).openConnection(
Proxy( Proxy(
Proxy.Type.HTTP, Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", httpPort) InetSocketAddress(LOOPBACK, httpPort)
) )
) as HttpURLConnection ) as HttpURLConnection
} }
@@ -4,16 +4,17 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.HY2
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.* import com.v2ray.ang.dto.*
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.fmt.Hysteria2Fmt
import com.v2ray.ang.util.fmt.ShadowsocksFmt import com.v2ray.ang.util.fmt.ShadowsocksFmt
import com.v2ray.ang.util.fmt.SocksFmt import com.v2ray.ang.util.fmt.SocksFmt
import com.v2ray.ang.util.fmt.TrojanFmt import com.v2ray.ang.util.fmt.TrojanFmt
@@ -31,6 +32,7 @@ object AngConfigManager {
private fun parseConfig( private fun parseConfig(
str: String?, str: String?,
subid: String, subid: String,
subItem: SubscriptionItem?,
removedSelectedServer: ServerConfig? removedSelectedServer: ServerConfig?
): Int { ): Int {
try { try {
@@ -39,17 +41,19 @@ object AngConfigManager {
} }
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) { val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
VmessFmt.parseVmess(str) VmessFmt.parse(str)
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) { } else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
ShadowsocksFmt.parseShadowsocks(str) ShadowsocksFmt.parse(str)
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) { } else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
SocksFmt.parseSocks(str) SocksFmt.parse(str)
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) { } else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
TrojanFmt.parseTrojan(str) TrojanFmt.parse(str)
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) { } else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
VlessFmt.parseVless(str) VlessFmt.parse(str)
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) { } else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
WireguardFmt.parseWireguard(str) WireguardFmt.parse(str)
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme) || str.startsWith(HY2)) {
Hysteria2Fmt.parse(str)
} else { } else {
null null
} }
@@ -57,6 +61,13 @@ object AngConfigManager {
if (config == null) { if (config == null) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
//filter
if (subItem?.filter != null && subItem.filter?.isNotEmpty() == true && config.remarks.isNotEmpty()) {
val matched = Regex(pattern = subItem.filter ?: "")
.containsMatchIn(input = config.remarks)
if (!matched) return -1
}
config.subscriptionId = subid config.subscriptionId = subid
val guid = MmkvManager.encodeServerConfig("", config) val guid = MmkvManager.encodeServerConfig("", config)
if (removedSelectedServer != null && if (removedSelectedServer != null &&
@@ -92,6 +103,7 @@ object AngConfigManager {
EConfigType.VLESS -> VlessFmt.toUri(config) EConfigType.VLESS -> VlessFmt.toUri(config)
EConfigType.TROJAN -> TrojanFmt.toUri(config) EConfigType.TROJAN -> TrojanFmt.toUri(config)
EConfigType.WIREGUARD -> WireguardFmt.toUri(config) EConfigType.WIREGUARD -> WireguardFmt.toUri(config)
EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -240,11 +252,12 @@ object AngConfigManager {
MmkvManager.removeServerViaSubid(subid) MmkvManager.removeServerViaSubid(subid)
} }
val subItem = MmkvManager.decodeSubscription(subid)
var count = 0 var count = 0
servers.lines() servers.lines()
.reversed() .reversed()
.forEach { .forEach {
val resId = parseConfig(it, subid, removedSelectedServer) val resId = parseConfig(it, subid, subItem, removedSelectedServer)
if (resId == 0) { if (resId == 0) {
count++ count++
} }
@@ -265,34 +278,21 @@ object AngConfigManager {
&& server.contains("routing") && server.contains("routing")
) { ) {
try { try {
//val gson = GsonBuilder().setPrettyPrinting().create()
val gson = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
JsonPrimitive(
src?.toInt()
)
}
)
.create()
val serverList: Array<Any> = val serverList: Array<Any> =
Gson().fromJson(server, Array<Any>::class.java) JsonUtil.fromJson(server, Array<Any>::class.java)
if (serverList.isNotEmpty()) { if (serverList.isNotEmpty()) {
var count = 0 var count = 0
for (srv in serverList.reversed()) { for (srv in serverList.reversed()) {
val config = ServerConfig.create(EConfigType.CUSTOM) val config = ServerConfig.create(EConfigType.CUSTOM)
config.fullConfig = config.fullConfig =
Gson().fromJson(Gson().toJson(srv), V2rayConfig::class.java) JsonUtil.fromJson(JsonUtil.toJson(srv), V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks config.remarks = config.fullConfig?.remarks
?: ("%04d-".format(count + 1) + System.currentTimeMillis() ?: ("%04d-".format(count + 1) + System.currentTimeMillis()
.toString()) .toString())
config.subscriptionId = subid config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, gson.toJson(srv)) MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(srv))
count += 1 count += 1
} }
return count return count
@@ -301,21 +301,31 @@ object AngConfigManager {
e.printStackTrace() e.printStackTrace()
} }
// For compatibility try {
val config = ServerConfig.create(EConfigType.CUSTOM) // For compatibility
config.subscriptionId = subid val config = ServerConfig.create(EConfigType.CUSTOM)
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.subscriptionId = subid
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString() config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config) config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
MmkvManager.encodeServerRaw(key, server) val key = MmkvManager.encodeServerConfig("", config)
return 1 MmkvManager.encodeServerRaw(key, server)
return 1
} catch (e: Exception) {
e.printStackTrace()
}
return 0
} else if (server.startsWith("[Interface]") && server.contains("[Peer]")) { } else if (server.startsWith("[Interface]") && server.contains("[Peer]")) {
val config = WireguardFmt.parseWireguardConfFile(server) try {
?: return R.string.toast_incorrect_protocol val config = WireguardFmt.parseWireguardConfFile(server)
config.fullConfig?.remarks ?: System.currentTimeMillis().toString() ?: return R.string.toast_incorrect_protocol
val key = MmkvManager.encodeServerConfig("", config) config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
MmkvManager.encodeServerRaw(key, server) val key = MmkvManager.encodeServerConfig("", config)
return 1 MmkvManager.encodeServerRaw(key, server)
return 1
} catch (e: Exception) {
e.printStackTrace()
}
return 0
} else { } else {
return 0 return 0
} }
@@ -350,19 +360,17 @@ object AngConfigManager {
return 0 return 0
} }
Log.d(AppConfig.ANG_PACKAGE, url) Log.d(AppConfig.ANG_PACKAGE, url)
var configText = try { var configText = try {
Utils.getUrlContentWithCustomUserAgent(url) val httpPort = SettingsManager.getHttpPort()
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
"" ""
} }
if (configText.isEmpty()) { if (configText.isEmpty()) {
configText = try { configText = try {
val httpPort = Utils.parseInt( Utils.getUrlContentWithCustomUserAgent(url)
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
AppConfig.PORT_HTTP.toInt()
)
Utils.getUrlContentWithCustomUserAgent(url, 30000, httpPort)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
"" ""
@@ -0,0 +1,37 @@
package com.v2ray.ang.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
object JsonUtil {
private var gson = Gson()
fun toJson(src: Any?): String {
return gson.toJson(src)
}
fun <T> fromJson(json: String, cls: Class<T>): T {
return gson.fromJson(json, cls)
}
fun toJsonPretty(src: Any?): String {
val gsonPre = GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? ->
JsonPrimitive(
src?.toInt()
)
}
)
.create()
return gsonPre.toJson(src)
}
}
@@ -1,7 +1,8 @@
package com.v2ray.ang.util package com.v2ray.ang.util
import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig.PREF_IS_BOOTED
import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET
import com.v2ray.ang.dto.AssetUrlItem import com.v2ray.ang.dto.AssetUrlItem
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
@@ -48,7 +49,7 @@ object MmkvManager {
} }
fun encodeServerList(serverList: MutableList<String>) { fun encodeServerList(serverList: MutableList<String>) {
mainStorage.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList)) mainStorage.encode(KEY_ANG_CONFIGS, JsonUtil.toJson(serverList))
} }
fun decodeServerList(): MutableList<String> { fun decodeServerList(): MutableList<String> {
@@ -56,7 +57,7 @@ object MmkvManager {
return if (json.isNullOrBlank()) { return if (json.isNullOrBlank()) {
mutableListOf() mutableListOf()
} else { } else {
Gson().fromJson(json, Array<String>::class.java).toMutableList() JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
} }
} }
@@ -68,7 +69,7 @@ object MmkvManager {
if (json.isNullOrBlank()) { if (json.isNullOrBlank()) {
return null return null
} }
return Gson().fromJson(json, ServerConfig::class.java) return JsonUtil.fromJson(json, ServerConfig::class.java)
} }
fun decodeProfileConfig(guid: String): ProfileItem? { fun decodeProfileConfig(guid: String): ProfileItem? {
@@ -79,12 +80,12 @@ object MmkvManager {
if (json.isNullOrBlank()) { if (json.isNullOrBlank()) {
return null return null
} }
return Gson().fromJson(json, ProfileItem::class.java) return JsonUtil.fromJson(json, ProfileItem::class.java)
} }
fun encodeServerConfig(guid: String, config: ServerConfig): String { fun encodeServerConfig(guid: String, config: ServerConfig): String {
val key = guid.ifBlank { Utils.getUuid() } val key = guid.ifBlank { Utils.getUuid() }
serverStorage.encode(key, Gson().toJson(config)) serverStorage.encode(key, JsonUtil.toJson(config))
val serverList = decodeServerList() val serverList = decodeServerList()
if (!serverList.contains(key)) { if (!serverList.contains(key)) {
serverList.add(0, key) serverList.add(0, key)
@@ -100,7 +101,7 @@ object MmkvManager {
server = config.getProxyOutbound()?.getServerAddress(), server = config.getProxyOutbound()?.getServerAddress(),
serverPort = config.getProxyOutbound()?.getServerPort(), serverPort = config.getProxyOutbound()?.getServerPort(),
) )
profileStorage.encode(key, Gson().toJson(profile)) profileStorage.encode(key, JsonUtil.toJson(profile))
return key return key
} }
@@ -140,7 +141,7 @@ object MmkvManager {
if (json.isNullOrBlank()) { if (json.isNullOrBlank()) {
return null return null
} }
return Gson().fromJson(json, ServerAffiliationInfo::class.java) return JsonUtil.fromJson(json, ServerAffiliationInfo::class.java)
} }
fun encodeServerTestDelayMillis(guid: String, testResult: Long) { fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
@@ -149,14 +150,14 @@ object MmkvManager {
} }
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo() val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
aff.testDelayMillis = testResult aff.testDelayMillis = testResult
serverAffStorage.encode(guid, Gson().toJson(aff)) serverAffStorage.encode(guid, JsonUtil.toJson(aff))
} }
fun clearAllTestDelayResults(keys: List<String>?) { fun clearAllTestDelayResults(keys: List<String>?) {
keys?.forEach { key -> keys?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff -> decodeServerAffiliationInfo(key)?.let { aff ->
aff.testDelayMillis = 0 aff.testDelayMillis = 0
serverAffStorage.encode(key, Gson().toJson(aff)) serverAffStorage.encode(key, JsonUtil.toJson(aff))
} }
} }
} }
@@ -216,7 +217,7 @@ object MmkvManager {
decodeSubsList().forEach { key -> decodeSubsList().forEach { key ->
val json = subStorage.decodeString(key) val json = subStorage.decodeString(key)
if (!json.isNullOrBlank()) { if (!json.isNullOrBlank()) {
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java))) subscriptions.add(Pair(key, JsonUtil.fromJson(json, SubscriptionItem::class.java)))
} }
} }
return subscriptions return subscriptions
@@ -233,7 +234,7 @@ object MmkvManager {
fun encodeSubscription(guid: String, subItem: SubscriptionItem) { fun encodeSubscription(guid: String, subItem: SubscriptionItem) {
val key = guid.ifBlank { Utils.getUuid() } val key = guid.ifBlank { Utils.getUuid() }
subStorage.encode(key, Gson().toJson(subItem)) subStorage.encode(key, JsonUtil.toJson(subItem))
val subsList = decodeSubsList() val subsList = decodeSubsList()
if (!subsList.contains(key)) { if (!subsList.contains(key)) {
@@ -244,11 +245,11 @@ object MmkvManager {
fun decodeSubscription(subscriptionId: String): SubscriptionItem? { fun decodeSubscription(subscriptionId: String): SubscriptionItem? {
val json = subStorage.decodeString(subscriptionId) ?: return null val json = subStorage.decodeString(subscriptionId) ?: return null
return Gson().fromJson(json, SubscriptionItem::class.java) return JsonUtil.fromJson(json, SubscriptionItem::class.java)
} }
fun encodeSubsList(subsList: MutableList<String>) { fun encodeSubsList(subsList: MutableList<String>) {
mainStorage.encode(KEY_SUB_IDS, Gson().toJson(subsList)) mainStorage.encode(KEY_SUB_IDS, JsonUtil.toJson(subsList))
} }
fun decodeSubsList(): MutableList<String> { fun decodeSubsList(): MutableList<String> {
@@ -256,7 +257,7 @@ object MmkvManager {
return if (json.isNullOrBlank()) { return if (json.isNullOrBlank()) {
mutableListOf() mutableListOf()
} else { } else {
Gson().fromJson(json, Array<String>::class.java).toMutableList() JsonUtil.fromJson(json, Array<String>::class.java).toMutableList()
} }
} }
@@ -269,7 +270,7 @@ object MmkvManager {
assetStorage.allKeys()?.forEach { key -> assetStorage.allKeys()?.forEach { key ->
val json = assetStorage.decodeString(key) val json = assetStorage.decodeString(key)
if (!json.isNullOrBlank()) { if (!json.isNullOrBlank()) {
assetUrlItems.add(Pair(key, Gson().fromJson(json, AssetUrlItem::class.java))) assetUrlItems.add(Pair(key, JsonUtil.fromJson(json, AssetUrlItem::class.java)))
} }
} }
return assetUrlItems.sortedBy { (_, value) -> value.addedTime } return assetUrlItems.sortedBy { (_, value) -> value.addedTime }
@@ -281,12 +282,12 @@ object MmkvManager {
fun encodeAsset(assetid: String, assetItem: AssetUrlItem) { fun encodeAsset(assetid: String, assetItem: AssetUrlItem) {
val key = assetid.ifBlank { Utils.getUuid() } val key = assetid.ifBlank { Utils.getUuid() }
assetStorage.encode(key, Gson().toJson(assetItem)) assetStorage.encode(key, JsonUtil.toJson(assetItem))
} }
fun decodeAsset(assetid: String): AssetUrlItem? { fun decodeAsset(assetid: String): AssetUrlItem? {
val json = assetStorage.decodeString(assetid) ?: return null val json = assetStorage.decodeString(assetid) ?: return null
return Gson().fromJson(json, AssetUrlItem::class.java) return JsonUtil.fromJson(json, AssetUrlItem::class.java)
} }
//endregion //endregion
@@ -296,15 +297,28 @@ object MmkvManager {
fun decodeRoutingRulesets(): MutableList<RulesetItem>? { fun decodeRoutingRulesets(): MutableList<RulesetItem>? {
val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET) val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET)
if (ruleset.isNullOrEmpty()) return null if (ruleset.isNullOrEmpty()) return null
return Gson().fromJson(ruleset, Array<RulesetItem>::class.java).toMutableList() return JsonUtil.fromJson(ruleset, Array<RulesetItem>::class.java).toMutableList()
} }
fun encodeRoutingRulesets(rulesetList: MutableList<RulesetItem>?) { fun encodeRoutingRulesets(rulesetList: MutableList<RulesetItem>?) {
if (rulesetList.isNullOrEmpty()) if (rulesetList.isNullOrEmpty())
settingsStorage.encode(PREF_ROUTING_RULESET, "") settingsStorage.encode(PREF_ROUTING_RULESET, "")
else else
settingsStorage.encode(PREF_ROUTING_RULESET, Gson().toJson(rulesetList)) settingsStorage.encode(PREF_ROUTING_RULESET, JsonUtil.toJson(rulesetList))
} }
//endregion //endregion
//region Others
fun encodeStartOnBoot(startOnBoot: Boolean) {
settingsStorage.encode(PREF_IS_BOOTED, startOnBoot)
}
fun decodeStartOnBoot(): Boolean {
return settingsStorage.decodeBool(PREF_IS_BOOTED, false)
}
//endregion
} }
@@ -0,0 +1,99 @@
package com.v2ray.ang.util
import android.content.Context
import android.os.SystemClock
import android.util.Log
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.fmt.Hysteria2Fmt
import java.io.File
object PluginUtil {
//private const val HYSTERIA2 = "hysteria2-plugin"
private const val HYSTERIA2 = "libhysteria2.so"
private const val TAG = ANG_PACKAGE
private lateinit var procService: ProcessService
// fun initPlugin(name: String): PluginManager.InitResult {
// return PluginManager.init(name)!!
// }
fun runPlugin(context: Context, config: ServerConfig?, domainPort: String?) {
Log.d(TAG, "runPlugin")
val outbound = config?.getProxyOutbound() ?: return
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
val configFile = genConfigHy2(context, config, domainPort) ?: return
val cmd = genCmdHy2(context, configFile)
procService = ProcessService()
procService.runProcess(context, cmd)
}
}
fun stopPlugin() {
stopHy2()
}
fun realPingHy2(context: Context, config: ServerConfig?): Long {
Log.d(TAG, "realPingHy2")
val retFailure = -1L
val outbound = config?.getProxyOutbound() ?: return retFailure
if (outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)) {
val socksPort = Utils.findFreePort(listOf(0))
val configFile = genConfigHy2(context, config, "0:${socksPort}") ?: return retFailure
val cmd = genCmdHy2(context, configFile)
val proc = ProcessService()
proc.runProcess(context, cmd)
Thread.sleep(1000L)
val delay = SpeedtestUtil.testConnection(context, socksPort)
proc.stopProcess()
return delay.first
}
return retFailure
}
private fun genConfigHy2(context: Context, config: ServerConfig, domainPort: String?): File? {
Log.d(TAG, "runPlugin $HYSTERIA2")
val socksPort = domainPort?.split(":")?.last()
.let { if (it.isNullOrEmpty()) return null else it.toInt() }
val hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return null
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
Log.d(TAG, "runPlugin ${configFile.absolutePath}")
configFile.parentFile?.mkdirs()
configFile.writeText(JsonUtil.toJson(hy2Config))
Log.d(TAG, JsonUtil.toJson(hy2Config))
return configFile
}
private fun genCmdHy2(context: Context, configFile: File): MutableList<String> {
return mutableListOf(
File(context.applicationInfo.nativeLibraryDir, HYSTERIA2).absolutePath,
//initPlugin(HYSTERIA2).path,
"--disable-update-check",
"--config",
configFile.absolutePath,
"--log-level",
"warn",
"client"
)
}
private fun stopHy2() {
try {
Log.d(TAG, "$HYSTERIA2 destroy")
procService?.stopProcess()
} catch (e: Exception) {
Log.d(TAG, e.toString())
}
}
}
@@ -2,39 +2,80 @@ package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import com.google.gson.Gson
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.util.MmkvManager.decodeProfileConfig import com.v2ray.ang.util.MmkvManager.decodeProfileConfig
import com.v2ray.ang.util.MmkvManager.decodeServerConfig import com.v2ray.ang.util.MmkvManager.decodeServerConfig
import com.v2ray.ang.util.MmkvManager.decodeServerList import com.v2ray.ang.util.MmkvManager.decodeServerList
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils.parseInt
import java.util.Collections import java.util.Collections
object SettingsManager { object SettingsManager {
fun initRoutingRulesets(context: Context, index: Int = 0) { fun initRoutingRulesets(context: Context) {
val exist = MmkvManager.decodeRoutingRulesets() val exist = MmkvManager.decodeRoutingRulesets()
val fileName = when (index) {
0 -> "custom_routing_white"
1 -> "custom_routing_black"
2 -> "custom_routing_global"
else -> "custom_routing_white"
}
if (exist.isNullOrEmpty()) { if (exist.isNullOrEmpty()) {
val assets = Utils.readTextFromAssets(context, fileName) val rulesetList = getPresetRoutingRulesets(context)
if (TextUtils.isEmpty(assets)) {
return
}
val rulesetList = Gson().fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
MmkvManager.encodeRoutingRulesets(rulesetList) MmkvManager.encodeRoutingRulesets(rulesetList)
} }
} }
private fun getPresetRoutingRulesets(context: Context, index: Int = 0): MutableList<RulesetItem>? {
val fileName = when (index) {
0 -> "custom_routing_white"
1 -> "custom_routing_black"
2 -> "custom_routing_global"
3 -> "custom_routing_white_iran"
else -> "custom_routing_white"
}
val assets = Utils.readTextFromAssets(context, fileName)
if (TextUtils.isEmpty(assets)) {
return null
}
return JsonUtil.fromJson(assets, Array<RulesetItem>::class.java).toMutableList()
}
fun resetRoutingRulesets(context: Context, index: Int) { fun resetRoutingRulesets(context: Context, index: Int) {
MmkvManager.encodeRoutingRulesets(null) val rulesetList = getPresetRoutingRulesets(context, index) ?: return
initRoutingRulesets(context, index) resetRoutingRulesetsCommon(rulesetList)
}
fun resetRoutingRulesetsFromClipboard(content: String?): Boolean {
if (content.isNullOrEmpty()) {
return false
}
try {
val rulesetList = JsonUtil.fromJson(content, Array<RulesetItem>::class.java).toMutableList()
if (rulesetList.isNullOrEmpty()) {
return false
}
resetRoutingRulesetsCommon(rulesetList)
return true
} catch (e: Exception) {
e.printStackTrace()
return false
}
}
private fun resetRoutingRulesetsCommon(rulesetList: MutableList<RulesetItem>) {
val rulesetNew: MutableList<RulesetItem> = mutableListOf()
MmkvManager.decodeRoutingRulesets()?.forEach { key ->
if (key.looked == true) {
rulesetNew.add(key)
}
}
rulesetNew.addAll(rulesetList)
MmkvManager.encodeRoutingRulesets(rulesetNew)
} }
fun getRoutingRuleset(index: Int): RulesetItem? { fun getRoutingRuleset(index: Int): RulesetItem? {
@@ -72,7 +113,9 @@ object SettingsManager {
fun routingRulesetsBypassLan(): Boolean { fun routingRulesetsBypassLan(): Boolean {
val rulesetItems = MmkvManager.decodeRoutingRulesets() val rulesetItems = MmkvManager.decodeRoutingRulesets()
val exist = rulesetItems?.any { it.enabled && it.domain?.contains(":private") == true } val exist = rulesetItems?.filter { it.enabled && it.outboundTag == TAG_DIRECT }?.any {
it.domain?.contains(GEOSITE_PRIVATE) == true || it.ip?.contains(GEOIP_PRIVATE) == true
}
return exist == true return exist == true
} }
@@ -106,4 +149,12 @@ object SettingsManager {
return null return null
} }
fun getSocksPort(): Int {
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
}
fun getHttpPort(): Int {
return parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
}
} }
@@ -5,6 +5,7 @@ import android.os.SystemClock
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.responseLength import com.v2ray.ang.extension.responseLength
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@@ -97,9 +98,9 @@ object SpeedtestUtil {
} }
} }
fun testConnection(context: Context, port: Int): String { fun testConnection(context: Context, port: Int): Pair<Long, String> {
// return V2RayVpnService.measureV2rayDelay()
var result: String var result: String
var elapsed = -1L
var conn: HttpURLConnection? = null var conn: HttpURLConnection? = null
try { try {
@@ -108,7 +109,7 @@ object SpeedtestUtil {
conn = url.openConnection( conn = url.openConnection(
Proxy( Proxy(
Proxy.Type.HTTP, Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", port) InetSocketAddress(LOOPBACK, port)
) )
) as HttpURLConnection ) as HttpURLConnection
conn.connectTimeout = 30000 conn.connectTimeout = 30000
@@ -119,7 +120,7 @@ object SpeedtestUtil {
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val code = conn.responseCode val code = conn.responseCode
val elapsed = SystemClock.elapsedRealtime() - start elapsed = SystemClock.elapsedRealtime() - start
if (code == 204 || code == 200 && conn.responseLength == 0L) { if (code == 204 || code == 200 && conn.responseLength == 0L) {
result = context.getString(R.string.connection_test_available, elapsed) result = context.getString(R.string.connection_test_available, elapsed)
@@ -133,10 +134,7 @@ object SpeedtestUtil {
} }
} catch (e: IOException) { } catch (e: IOException) {
// network exception // network exception
Log.d( Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
AppConfig.ANG_PACKAGE,
"testConnection IOException: " + Log.getStackTraceString(e)
)
result = context.getString(R.string.connection_test_error, e.message) result = context.getString(R.string.connection_test_error, e.message)
} catch (e: Exception) { } catch (e: Exception) {
// library exception, eg sumsung // library exception, eg sumsung
@@ -146,7 +144,7 @@ object SpeedtestUtil {
conn?.disconnect() conn?.disconnect()
} }
return result return Pair(elapsed, result)
} }
fun getLibVersion(): String { fun getLibVersion(): String {
@@ -19,6 +19,7 @@ import android.webkit.URLUtil
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
@@ -359,7 +360,7 @@ object Utils {
url.openConnection( url.openConnection(
Proxy( Proxy(
Proxy.Type.HTTP, Proxy.Type.HTTP,
InetSocketAddress("127.0.0.1", httpPort) InetSocketAddress(LOOPBACK, httpPort)
) )
) )
} }
@@ -452,6 +453,17 @@ object Utils {
} }
} }
fun findFreePort(ports: List<Int>): Int {
for (port in ports) {
try {
return ServerSocket(port).use { it.localPort }
} catch (ex: IOException) {
continue // try next port
}
}
// if the program gets here, no port in the range was found
throw IOException("no free port found")
}
} }
@@ -3,9 +3,13 @@ package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.Gson
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.GEOIP_CN
import com.v2ray.ang.AppConfig.GEOSITE_CN
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM import com.v2ray.ang.AppConfig.PROTOCOL_FREEDOM
import com.v2ray.ang.AppConfig.TAG_BLOCKED import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_DIRECT
@@ -13,6 +17,7 @@ import com.v2ray.ang.AppConfig.TAG_FRAGMENT
import com.v2ray.ang.AppConfig.TAG_PROXY import com.v2ray.ang.AppConfig.TAG_PROXY
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
@@ -24,33 +29,32 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
object V2rayConfigUtil { object V2rayConfigUtil {
data class Result(var status: Boolean, var content: String = "", var domainPort: String? = null) fun getV2rayConfig(context: Context, guid: String): ConfigResult {
fun getV2rayConfig(context: Context, guid: String): Result {
try { try {
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false) val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
if (config.configType == EConfigType.CUSTOM) { if (config.configType == EConfigType.CUSTOM) {
val raw = MmkvManager.decodeServerRaw(guid) val raw = MmkvManager.decodeServerRaw(guid)
val customConfig = if (raw.isNullOrBlank()) { val customConfig = if (raw.isNullOrBlank()) {
config.fullConfig?.toPrettyPrinting() ?: return Result(false) config.fullConfig?.toPrettyPrinting() ?: return ConfigResult(false)
} else { } else {
raw raw
} }
val domainPort = config.getProxyOutbound()?.getServerAddressAndPort() val domainPort = config.getProxyOutbound()?.getServerAddressAndPort()
return Result(true, customConfig, domainPort) return ConfigResult(true, guid, customConfig, domainPort)
} }
val result = getV2rayNonCustomConfig(context, config) val result = getV2rayNonCustomConfig(context, config)
Log.d(ANG_PACKAGE, result.content) //Log.d(ANG_PACKAGE, result.content)
result.guid = guid
return result return result
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return Result(false) return ConfigResult(false)
} }
} }
private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): Result { private fun getV2rayNonCustomConfig(context: Context, config: ServerConfig): ConfigResult {
val result = Result(false) val result = ConfigResult(false)
val outbound = config.getProxyOutbound() ?: return result val outbound = config.getProxyOutbound() ?: return result
val address = outbound.getServerAddress() ?: return result val address = outbound.getServerAddress() ?: return result
@@ -66,15 +70,16 @@ object V2rayConfigUtil {
if (TextUtils.isEmpty(assets)) { if (TextUtils.isEmpty(assets)) {
return result return result
} }
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result val v2rayConfig = JsonUtil.fromJson(assets, V2rayConfig::class.java) ?: return result
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning" v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks v2rayConfig.remarks = config.remarks
inbounds(v2rayConfig) inbounds(v2rayConfig)
outbounds(v2rayConfig, outbound) val isPlugin = outbound.protocol.equals(EConfigType.HYSTERIA2.name, true)
val retOut = outbounds(v2rayConfig, outbound, isPlugin)
val retMore = moreOutbounds(v2rayConfig, config.subscriptionId) val retMore = moreOutbounds(v2rayConfig, config.subscriptionId, isPlugin)
routing(v2rayConfig) routing(v2rayConfig)
@@ -92,25 +97,19 @@ object V2rayConfigUtil {
result.status = true result.status = true
result.content = v2rayConfig.toPrettyPrinting() result.content = v2rayConfig.toPrettyPrinting()
result.domainPort = if (retMore.first) retMore.second else outbound.getServerAddressAndPort() result.domainPort = if (retMore.first) retMore.second else retOut.second
return result return result
} }
private fun inbounds(v2rayConfig: V2rayConfig): Boolean { private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try { try {
val socksPort = Utils.parseInt( val socksPort = SettingsManager.getSocksPort()
settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), val httpPort = SettingsManager.getHttpPort()
AppConfig.PORT_SOCKS.toInt()
)
val httpPort = Utils.parseInt(
settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT),
AppConfig.PORT_HTTP.toInt()
)
v2rayConfig.inbounds.forEach { curInbound -> v2rayConfig.inbounds.forEach { curInbound ->
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) { if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
//bind all inbounds to localhost if the user requests //bind all inbounds to localhost if the user requests
curInbound.listen = "127.0.0.1" curInbound.listen = LOOPBACK
} }
} }
v2rayConfig.inbounds[0].port = socksPort v2rayConfig.inbounds[0].port = socksPort
@@ -144,9 +143,31 @@ object V2rayConfigUtil {
return true return true
} }
private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean): Boolean { private fun outbounds(v2rayConfig: V2rayConfig, outbound: V2rayConfig.OutboundBean, isPlugin: Boolean): Pair<Boolean, String> {
if (isPlugin) {
val socksPort = Utils.findFreePort(listOf(100 + SettingsManager.getSocksPort(), 0))
val outboundNew = V2rayConfig.OutboundBean(
mux = null,
protocol = EConfigType.SOCKS.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
servers = listOf(
V2rayConfig.OutboundBean.OutSettingsBean.ServersBean(
address = LOOPBACK,
port = socksPort
)
)
)
)
if (v2rayConfig.outbounds.isNotEmpty()) {
v2rayConfig.outbounds[0] = outboundNew
} else {
v2rayConfig.outbounds.add(outboundNew)
}
return Pair(true, outboundNew.getServerAddressAndPort())
}
val ret = updateOutboundWithGlobalSettings(outbound) val ret = updateOutboundWithGlobalSettings(outbound)
if (!ret) return false if (!ret) return Pair(false, "")
if (v2rayConfig.outbounds.isNotEmpty()) { if (v2rayConfig.outbounds.isNotEmpty()) {
v2rayConfig.outbounds[0] = outbound v2rayConfig.outbounds[0] = outbound
@@ -155,7 +176,7 @@ object V2rayConfigUtil {
} }
updateOutboundFragment(v2rayConfig) updateOutboundFragment(v2rayConfig)
return true return Pair(true, outbound.getServerAddressAndPort())
} }
private fun fakedns(v2rayConfig: V2rayConfig) { private fun fakedns(v2rayConfig: V2rayConfig) {
@@ -188,7 +209,7 @@ object V2rayConfigUtil {
return return
} }
val rule = Gson().fromJson(Gson().toJson(item), RulesBean::class.java) ?: return val rule = JsonUtil.fromJson(JsonUtil.toJson(item), RulesBean::class.java) ?: return
v2rayConfig.routing.rules.add(rule) v2rayConfig.routing.rules.add(rule)
@@ -204,7 +225,9 @@ object V2rayConfigUtil {
rulesetItems?.forEach { key -> rulesetItems?.forEach { key ->
if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) { if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) {
key.domain?.forEach { key.domain?.forEach {
if (it.startsWith("geosite:") || it.startsWith("domain:")) { if (it != GEOSITE_PRIVATE
&& (it.startsWith("geosite:") || it.startsWith("domain:"))
) {
domain.add(it) domain.add(it)
} }
} }
@@ -217,7 +240,7 @@ object V2rayConfigUtil {
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean { private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try { try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf("geosite:cn") val geositeCn = arrayListOf(GEOSITE_CN)
val proxyDomain = userRule2Domain(TAG_PROXY) val proxyDomain = userRule2Domain(TAG_PROXY)
val directDomain = userRule2Domain(TAG_DIRECT) val directDomain = userRule2Domain(TAG_DIRECT)
// fakedns with all domains to make it always top priority // fakedns with all domains to make it always top priority
@@ -247,7 +270,7 @@ object V2rayConfigUtil {
V2rayConfig.InboundBean( V2rayConfig.InboundBean(
tag = "dns-in", tag = "dns-in",
port = localDnsPort, port = localDnsPort,
listen = "127.0.0.1", listen = LOOPBACK,
protocol = "dokodemo-door", protocol = "dokodemo-door",
settings = dnsInboundSettings, settings = dnsInboundSettings,
sniffing = null sniffing = null
@@ -308,8 +331,8 @@ object V2rayConfigUtil {
// domestic DNS // domestic DNS
val domesticDns = Utils.getDomesticDnsServers() val domesticDns = Utils.getDomesticDnsServers()
val directDomain = userRule2Domain(TAG_DIRECT) val directDomain = userRule2Domain(TAG_DIRECT)
val isCnRoutingMode = directDomain.contains("geosite:cn") val isCnRoutingMode = directDomain.contains(GEOSITE_CN)
val geoipCn = arrayListOf("geoip:cn") val geoipCn = arrayListOf(GEOIP_CN)
if (directDomain.size > 0) { if (directDomain.size > 0) {
servers.add( servers.add(
V2rayConfig.DnsBean.ServersBean( V2rayConfig.DnsBean.ServersBean(
@@ -335,7 +358,7 @@ object V2rayConfigUtil {
//block dns //block dns
val blkDomain = userRule2Domain(TAG_BLOCKED) val blkDomain = userRule2Domain(TAG_BLOCKED)
if (blkDomain.size > 0) { if (blkDomain.size > 0) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" }) hosts.putAll(blkDomain.map { it to LOOPBACK })
} }
// hardcode googleapi rule to fix play store problems // hardcode googleapi rule to fix play store problems
@@ -380,6 +403,7 @@ object V2rayConfigUtil {
|| protocol.equals(EConfigType.HTTP.name, true) || protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true) || protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.WIREGUARD.name, true) || protocol.equals(EConfigType.WIREGUARD.name, true)
|| protocol.equals(EConfigType.HYSTERIA2.name, true)
) { ) {
muxEnabled = false muxEnabled = false
} else if (protocol.equals(EConfigType.VLESS.name, true) } else if (protocol.equals(EConfigType.VLESS.name, true)
@@ -421,7 +445,7 @@ object V2rayConfigUtil {
val requestString: String by lazy { val requestString: String by lazy {
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""" """{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
} }
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson( outbound.streamSettings?.tcpSettings?.header?.request = JsonUtil.fromJson(
requestString, requestString,
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
) )
@@ -508,10 +532,13 @@ object V2rayConfigUtil {
return true return true
} }
private fun moreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String): Pair<Boolean, String> { private fun moreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String, isPlugin: Boolean): Pair<Boolean, String> {
val returnPair = Pair(false, "") val returnPair = Pair(false, "")
var domainPort: String = "" var domainPort: String = ""
if (isPlugin) {
return returnPair
}
//fragment proxy //fragment proxy
if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) { if (settingsStorage?.decodeBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
return returnPair return returnPair
@@ -0,0 +1,104 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils
open class FmtBase {
fun toUri(address: String?, port: Int?, userInfo: String?, dicQuery: HashMap<String, String>?, remark: String): String {
val query = if (dicQuery != null)
("?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + Utils.urlEncode(it.second) }))
else ""
val url = String.format(
"%s@%s:%s",
Utils.urlEncode(userInfo ?: ""),
Utils.getIpv6Address(address),
port
)
return "${url}${query}#${Utils.urlEncode(remark)}"
}
fun getStdTransport(outbound: V2rayConfig.OutboundBean, streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean): HashMap<String, String> {
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = tlsSetting.spiderX.orEmpty()
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = transportDetails[2]
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = transportDetails[1]
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = transportDetails[2]
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = transportDetails[1]
dicQuery["key"] = transportDetails[2]
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = transportDetails[1]
dicQuery["serviceName"] = transportDetails[2]
}
}
}
return dicQuery
}
}
@@ -0,0 +1,102 @@
package com.v2ray.ang.util.fmt
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.Hysteria2Bean
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import java.net.URI
object Hysteria2Fmt : FmtBase() {
fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.HYSTERIA2)
val uri = URI(Utils.fixIllegalUrl(str))
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
val queryParam = uri.rawQuery.split("&")
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
config.outboundBean?.streamSettings?.populateTlsSettings(
V2rayConfig.TLS,
if ((queryParam["insecure"].orEmpty()) == "1") true else allowInsecure,
queryParam["sni"] ?: uri.idnHost,
null,
queryParam["alpn"],
null,
null,
null
)
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = uri.idnHost
server.port = uri.port
server.password = uri.userInfo
}
if (!queryParam["obfs-password"].isNullOrEmpty()) {
config.outboundBean?.settings?.obfsPassword = queryParam["obfs-password"]
}
return config
}
fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val dicQuery = HashMap<String, String>()
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
streamSetting.tlsSettings?.let { tlsSetting ->
dicQuery["insecure"] = if (tlsSetting.allowInsecure) "1" else "0"
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] = Utils.removeWhiteSpace(tlsSetting.alpn.joinToString(",")).orEmpty()
}
}
if (!outbound.settings?.obfsPassword.isNullOrEmpty()) {
dicQuery["obfs"] = "salamander"
dicQuery["obfs-password"] = outbound.settings?.obfsPassword ?: ""
}
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
}
fun toNativeConfig(config: ServerConfig, socksPort: Int): Hysteria2Bean? {
val outbound = config.getProxyOutbound() ?: return null
val tls = outbound.streamSettings?.tlsSettings
val obfs = if (outbound.settings?.obfsPassword.isNullOrEmpty()) null else
Hysteria2Bean.ObfsBean(
type = "salamander",
salamander = Hysteria2Bean.ObfsBean.SalamanderBean(
password = outbound.settings?.obfsPassword
)
)
val bean = Hysteria2Bean(
server = outbound.getServerAddressAndPort(),
auth = outbound.getPassword(),
obfs = obfs,
socks5 = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
http = Hysteria2Bean.Socks5Bean(
listen = "$LOOPBACK:${socksPort}",
),
tls = Hysteria2Bean.TlsBean(
sni = tls?.serverName ?: outbound.getServerAddress(),
insecure = tls?.allowInsecure
)
)
return bean
}
}
@@ -8,8 +8,8 @@ import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
object ShadowsocksFmt { object ShadowsocksFmt : FmtBase() {
fun parseShadowsocks(str: String): ServerConfig? { fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SHADOWSOCKS) val config = ServerConfig.create(EConfigType.SHADOWSOCKS)
if (!tryResolveResolveSip002(str, config)) { if (!tryResolveResolveSip002(str, config)) {
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "") var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
@@ -52,16 +52,10 @@ object ShadowsocksFmt {
fun toUri(config: ServerConfig): String { fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw = val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
val url = String.format( return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
"%s@%s:%s",
pw,
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
} }
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean { private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
@@ -5,8 +5,8 @@ import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
object SocksFmt { object SocksFmt : FmtBase() {
fun parseSocks(str: String): ServerConfig? { fun parse(str: String): ServerConfig? {
val config = ServerConfig.create(EConfigType.SOCKS) val config = ServerConfig.create(EConfigType.SOCKS)
var result = str.replace(EConfigType.SOCKS.protocolScheme, "") var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
val indexSplit = result.indexOf("#") val indexSplit = result.indexOf("#")
@@ -52,18 +52,13 @@ object SocksFmt {
fun toUri(config: ServerConfig): String { fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val pw = val pw =
if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null) if (outbound.settings?.servers?.get(0)?.users?.get(0)?.user != null)
"${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}" "${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}"
else else
":" ":"
val url = String.format(
"%s@%s:%s", return toUri(outbound.getServerAddress(), outbound.getServerPort(), pw, null, config.remarks)
Utils.encode(pw),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + remark
} }
} }
@@ -10,9 +10,9 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
object TrojanFmt { object TrojanFmt : FmtBase() {
fun parseTrojan(str: String): ServerConfig { fun parse(str: String): ServerConfig {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.TROJAN) val config = ServerConfig.create(EConfigType.TROJAN)
@@ -76,98 +76,15 @@ object TrojanFmt {
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean() val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>() val dicQuery = getStdTransport(outbound, streamSetting)
config.outboundBean?.settings?.servers?.get(0)?.flow?.let { config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) { if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it dicQuery["flow"] = it
} }
} }
dicQuery["security"] = streamSetting.security.ifEmpty { "none" } return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
} }
} }
@@ -10,9 +10,9 @@ import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
object VlessFmt { object VlessFmt : FmtBase() {
fun parseVless(str: String): ServerConfig? { fun parse(str: String): ServerConfig? {
var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false var allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
val config = ServerConfig.create(EConfigType.VLESS) val config = ServerConfig.create(EConfigType.VLESS)
@@ -63,8 +63,9 @@ object VlessFmt {
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean() val streamSetting = outbound.streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean()
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>() val dicQuery = getStdTransport(outbound, streamSetting)
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let { outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
if (!TextUtils.isEmpty(it)) { if (!TextUtils.isEmpty(it)) {
dicQuery["flow"] = it dicQuery["flow"] = it
@@ -74,91 +75,6 @@ object VlessFmt {
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none" if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
else outbound.getSecurityEncryption().orEmpty() else outbound.getSecurityEncryption().orEmpty()
return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
(streamSetting.tlsSettings
?: streamSetting.realitySettings)?.let { tlsSetting ->
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
dicQuery["sni"] = tlsSetting.serverName
}
if (!tlsSetting.alpn.isNullOrEmpty() && tlsSetting.alpn.isNotEmpty()) {
dicQuery["alpn"] =
Utils.removeWhiteSpace(tlsSetting.alpn.joinToString()).orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.fingerprint)) {
dicQuery["fp"] = tlsSetting.fingerprint.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.publicKey)) {
dicQuery["pbk"] = tlsSetting.publicKey.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.shortId)) {
dicQuery["sid"] = tlsSetting.shortId.orEmpty()
}
if (!TextUtils.isEmpty(tlsSetting.spiderX)) {
dicQuery["spx"] = Utils.urlEncode(tlsSetting.spiderX.orEmpty())
}
}
dicQuery["type"] =
streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
outbound.getTransportSettingDetails()?.let { transportDetails ->
when (streamSetting.network) {
"tcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
}
"kcp" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
}
}
"ws", "httpupgrade", "splithttp" -> {
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"http", "h2" -> {
dicQuery["type"] = "http"
if (!TextUtils.isEmpty(transportDetails[1])) {
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
}
if (!TextUtils.isEmpty(transportDetails[2])) {
dicQuery["path"] = Utils.urlEncode(transportDetails[2])
}
}
"quic" -> {
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
}
"grpc" -> {
dicQuery["mode"] = transportDetails[0]
dicQuery["authority"] = Utils.urlEncode(transportDetails[1])
dicQuery["serviceName"] = Utils.urlEncode(transportDetails[2])
}
}
}
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format(
"%s@%s:%s",
outbound.getPassword(),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
} }
} }
@@ -2,20 +2,21 @@ package com.v2ray.ang.util.fmt
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.Gson
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.VmessQRCode import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.idnHost import com.v2ray.ang.extension.idnHost
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
object VmessFmt { object VmessFmt : FmtBase() {
fun parseVmess(str: String): ServerConfig? { fun parse(str: String): ServerConfig? {
if (str.indexOf('?') > 0 && str.indexOf('&') > 0) { if (str.indexOf('?') > 0 && str.indexOf('&') > 0) {
return parseVmessStd(str) return parseVmessStd(str)
} }
@@ -29,7 +30,7 @@ object VmessFmt {
Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed") Log.d(AppConfig.ANG_PACKAGE, "R.string.toast_decoding_failed")
return null return null
} }
val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) val vmessQRCode = JsonUtil.fromJson(result, VmessQRCode::class.java)
// Although VmessQRCode fields are non null, looks like Gson may still create null fields // Although VmessQRCode fields are non null, looks like Gson may still create null fields
if (TextUtils.isEmpty(vmessQRCode.add) if (TextUtils.isEmpty(vmessQRCode.add)
|| TextUtils.isEmpty(vmessQRCode.port) || TextUtils.isEmpty(vmessQRCode.port)
@@ -93,14 +94,14 @@ object VmessFmt {
vmessQRCode.tls = streamSetting.security vmessQRCode.tls = streamSetting.security
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty() vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
vmessQRCode.alpn = vmessQRCode.alpn =
Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString()).orEmpty() Utils.removeWhiteSpace(streamSetting.tlsSettings?.alpn?.joinToString(",")).orEmpty()
vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty() vmessQRCode.fp = streamSetting.tlsSettings?.fingerprint.orEmpty()
outbound.getTransportSettingDetails()?.let { transportDetails -> outbound.getTransportSettingDetails()?.let { transportDetails ->
vmessQRCode.type = transportDetails[0] vmessQRCode.type = transportDetails[0]
vmessQRCode.host = transportDetails[1] vmessQRCode.host = transportDetails[1]
vmessQRCode.path = transportDetails[2] vmessQRCode.path = transportDetails[2]
} }
val json = Gson().toJson(vmessQRCode) val json = JsonUtil.toJson(vmessQRCode)
return Utils.encode(json) return Utils.encode(json)
} }
@@ -8,8 +8,8 @@ import com.v2ray.ang.extension.removeWhiteSpace
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.net.URI import java.net.URI
object WireguardFmt { object WireguardFmt : FmtBase() {
fun parseWireguard(str: String): ServerConfig? { fun parse(str: String): ServerConfig? {
val uri = URI(Utils.fixIllegalUrl(str)) val uri = URI(Utils.fixIllegalUrl(str))
if (uri.rawQuery != null) { if (uri.rawQuery != null) {
val config = ServerConfig.create(EConfigType.WIREGUARD) val config = ServerConfig.create(EConfigType.WIREGUARD)
@@ -71,37 +71,22 @@ object WireguardFmt {
} }
fun toUri(config: ServerConfig): String { fun toUri(config: ServerConfig): String {
val outbound = config.getProxyOutbound() ?: return "" val outbound = config.getProxyOutbound() ?: return ""
val remark = "#" + Utils.urlEncode(config.remarks)
val dicQuery = HashMap<String, String>() val dicQuery = HashMap<String, String>()
dicQuery["publickey"] = dicQuery["publickey"] = outbound.settings?.peers?.get(0)?.publicKey.toString()
Utils.urlEncode(outbound.settings?.peers?.get(0)?.publicKey.toString())
if (outbound.settings?.reserved != null) { if (outbound.settings?.reserved != null) {
dicQuery["reserved"] = Utils.urlEncode( dicQuery["reserved"] = Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString(",")).toString()
Utils.removeWhiteSpace(outbound.settings?.reserved?.joinToString())
.toString()
)
} }
dicQuery["address"] = Utils.urlEncode( dicQuery["address"] = Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString(",")).toString()
Utils.removeWhiteSpace((outbound.settings?.address as List<*>).joinToString())
.toString()
)
if (outbound.settings?.mtu != null) { if (outbound.settings?.mtu != null) {
dicQuery["mtu"] = outbound.settings?.mtu.toString() dicQuery["mtu"] = outbound.settings?.mtu.toString()
} }
val query = "?" + dicQuery.toList().joinToString(
separator = "&",
transform = { it.first + "=" + it.second })
val url = String.format( return toUri(outbound.getServerAddress(), outbound.getServerPort(), outbound.getPassword(), dicQuery, config.remarks)
"%s@%s:%s",
Utils.urlEncode(outbound.getPassword().toString()),
Utils.getIpv6Address(outbound.getServerAddress()),
outbound.getServerPort()
)
return url + query + remark
} }
} }
@@ -11,7 +11,6 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
@@ -21,14 +20,15 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.ServersCache import com.v2ray.ang.dto.ServersCache
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub import com.v2ray.ang.util.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.SpeedtestUtil import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -98,7 +98,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
try { try {
val config = ServerConfig.create(EConfigType.CUSTOM) val config = ServerConfig.create(EConfigType.CUSTOM)
config.subscriptionId = subscriptionId config.subscriptionId = subscriptionId
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java) config.fullConfig = JsonUtil.fromJson(server, V2rayConfig::class.java)
config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString() config.remarks = config.fullConfig?.remarks ?: System.currentTimeMillis().toString()
val key = MmkvManager.encodeServerConfig("", config) val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, server) MmkvManager.encodeServerRaw(key, server)
@@ -211,14 +211,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
getApplication<AngApplication>().toast(R.string.connection_test_testing) getApplication<AngApplication>().toast(R.string.connection_test_testing)
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
for (item in serversCopy) { for (item in serversCopy) {
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid) MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, item.guid)
if (config.status) {
MessageUtil.sendMsg2TestService(
getApplication(),
AppConfig.MSG_MEASURE_CONFIG,
Pair(item.guid, config.content)
)
}
} }
} }
} }
@@ -394,11 +387,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
} }
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> { AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
val resultPair: Pair<String, Long> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val resultPair = intent.serializable<Pair<String, Long>>("content") ?: return
intent.getSerializableExtra("content", Pair::class.java) as Pair<String, Long>
} else {
intent.getSerializableExtra("content") as Pair<String, Long>
}
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second) MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
updateListAction.value = getPosition(resultPair.first) updateListAction.value = getPosition(resultPair.first)
} }
@@ -49,6 +49,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
} }
AppConfig.PREF_ROUTE_ONLY_ENABLED, AppConfig.PREF_ROUTE_ONLY_ENABLED,
AppConfig.PREF_IS_BOOTED,
AppConfig.PREF_SPEED_ENABLED, AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_PROXY_SHARING, AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_LOCAL_DNS_ENABLED, AppConfig.PREF_LOCAL_DNS_ENABLED,
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M742.4,409.6L716.8,409.6L716.8,332.8C716.8,205.8 613.4,102.4 486.4,102.4S256,205.8 256,332.8L256,409.6h-25.6C188.1,409.6 153.6,444.1 153.6,486.4v409.6c0,42.3 34.5,76.8 76.8,76.8h512c42.3,0 76.8,-34.5 76.8,-76.8v-409.6c0,-42.3 -34.5,-76.8 -76.8,-76.8zM307.2,332.8C307.2,234 387.6,153.6 486.4,153.6S665.6,234 665.6,332.8L665.6,409.6L307.2,409.6L307.2,332.8zM768,896a25.6,25.6 0,0 1,-25.6 25.6h-512a25.6,25.6 0,0 1,-25.6 -25.6v-409.6a25.6,25.6 0,0 1,25.6 -25.6h512a25.6,25.6 0,0 1,25.6 25.6v409.6z" />
</vector>
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M742.4,409.6L716.8,409.6L716.8,332.8C716.8,205.8 613.4,102.4 486.4,102.4S256,205.8 256,332.8L256,409.6h-25.6C188.1,409.6 153.6,444.1 153.6,486.4v409.6c0,42.3 34.5,76.8 76.8,76.8h512c42.3,0 76.8,-34.5 76.8,-76.8v-409.6c0,-42.3 -34.5,-76.8 -76.8,-76.8zM307.2,332.8C307.2,234 387.6,153.6 486.4,153.6S665.6,234 665.6,332.8L665.6,409.6L307.2,409.6L307.2,332.8zM768,896a25.6,25.6 0,0 1,-25.6 25.6h-512a25.6,25.6 0,0 1,-25.6 -25.6v-409.6a25.6,25.6 0,0 1,25.6 -25.6h512a25.6,25.6 0,0 1,25.6 25.6v409.6z" />
</vector>
@@ -89,7 +89,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_marginBottom="24dp"> android:layout_marginBottom="12dp">
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
@@ -34,6 +34,19 @@
android:inputType="text" /> android:inputType="text" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_locked"
android:text="@string/routing_settings_locked"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.ServerActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height">
<include layout="@layout/layout_address_port" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_obfs_password" />
<EditText
android:id="@+id/et_obfs_password"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_id3" />
<EditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<include layout="@layout/layout_tls_hysteria2" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address3" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port3" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -117,102 +51,15 @@
android:entries="@array/ss_securitys" /> android:entries="@array/ss_securitys" />
</LinearLayout> </LinearLayout>
<LinearLayout <include layout="@layout/layout_transport" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView <include layout="@layout/layout_tls" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_request_host" />
<EditText
android:id="@+id/et_request_host"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_path" />
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<include layout="@layout/tls_layout" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height" android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address3" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port3" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -118,12 +52,11 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address3" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port3" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -98,101 +32,15 @@
</LinearLayout> </LinearLayout>
<LinearLayout <include layout="@layout/layout_transport" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView <include layout="@layout/layout_tls" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_request_host" />
<EditText
android:id="@+id/et_request_host"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_path" />
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<include layout="@layout/tls_layout" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height" android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -127,7 +61,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_lab_encryption" /> android:text="@string/server_lab_encryption" />
<EditText <EditText
android:id="@+id/et_security" android:id="@+id/et_security"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -137,101 +70,15 @@
</LinearLayout> </LinearLayout>
<LinearLayout <include layout="@layout/layout_transport" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView <include layout="@layout/layout_tls" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_request_host" />
<EditText
android:id="@+id/et_request_host"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_path" />
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<include layout="@layout/tls_layout" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height" android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -136,101 +70,15 @@
android:entries="@array/securitys" /> android:entries="@array/securitys" />
</LinearLayout> </LinearLayout>
<LinearLayout <include layout="@layout/layout_transport" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView <include layout="@layout/layout_tls" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_request_host" />
<EditText
android:id="@+id/et_request_host"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_path" />
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<include layout="@layout/tls_layout" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height" android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -11,73 +11,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height"> android:padding="@dimen/layout_margin_top_height">
<LinearLayout <include layout="@layout/layout_address_port" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address3" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port3" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -194,8 +128,8 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
@@ -72,6 +72,25 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub_setting_filter" />
<EditText
android:id="@+id/et_filter"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -34,11 +34,26 @@
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="@dimen/padding_start"> android:paddingStart="@dimen/padding_start">
<TextView <LinearLayout
android:id="@+id/remarks"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" /> android:orientation="horizontal">
<TextView
android:id="@+id/remarks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<ImageView
android:id="@+id/img_locked"
android:layout_gravity="center"
android:layout_marginStart="@dimen/padding_start"
android:layout_width="@dimen/padding"
android:layout_height="@dimen/padding"
app:srcCompat="@drawable/ic_lock_24dp" />
</LinearLayout>
<TextView <TextView
android:id="@+id/domainIp" android:id="@+id/domainIp"
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_server"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_remarks" />
<EditText
android:id="@+id/et_remarks"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/server_lab_address3" />
<EditText
android:id="@+id/et_address"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_port3" />
<EditText
android:id="@+id/et_port"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
</LinearLayout>
@@ -6,7 +6,7 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/l1" android:id="@+id/lay_stream_security"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_horizontal_margin"
@@ -28,7 +28,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l2" android:id="@+id/lay_sni"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_horizontal_margin"
@@ -49,7 +49,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l3" android:id="@+id/lay_stream_fingerprint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
@@ -70,7 +70,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l4" android:id="@+id/lay_stream_alpn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
@@ -91,7 +91,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l5" android:id="@+id/lay_allow_insecure"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@@ -109,7 +109,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l6" android:id="@+id/lay_public_key"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_horizontal_margin"
@@ -130,7 +130,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l7" android:id="@+id/lay_short_id"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_horizontal_margin"
@@ -151,7 +151,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/l8" android:id="@+id/lay_spider_x"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_horizontal_margin"
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<LinearLayout
android:id="@+id/lay_stream_security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_stream_security" />
<Spinner
android:id="@+id/sp_stream_security"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/streamsecuritys"
android:nextFocusDown="@+id/et_sni" />
</LinearLayout>
<LinearLayout
android:id="@+id/lay_sni"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_sni" />
<EditText
android:id="@+id/et_sni"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text"
android:nextFocusDown="@+id/sp_stream_fingerprint" />
</LinearLayout>
<LinearLayout
android:id="@+id/lay_allow_insecure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_allow_insecure" />
<Spinner
android:id="@+id/sp_allow_insecure"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/allowinsecures" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_request_host" />
<EditText
android:id="@+id/et_request_host"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_path" />
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
</LinearLayout>
@@ -48,6 +48,10 @@
android:id="@+id/import_manually_wireguard" android:id="@+id/import_manually_wireguard"
android:title="@string/menu_item_import_config_manually_wireguard" android:title="@string/menu_item_import_config_manually_wireguard"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/import_manually_hysteria2"
android:title="@string/menu_item_import_config_manually_hysteria2"
app:showAsAction="never" />
<item <item
android:title="@string/menu_item_import_config_custom" android:title="@string/menu_item_import_config_custom"
@@ -14,5 +14,13 @@
android:id="@+id/import_rulesets" android:id="@+id/import_rulesets"
android:title="@string/routing_settings_import_rulesets" android:title="@string/routing_settings_import_rulesets"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/import_rulesets_from_clipboard"
android:title="@string/routing_settings_import_rulesets_from_clipboard"
app:showAsAction="never" />
<item
android:id="@+id/export_rulesets_to_clipboard"
android:title="@string/routing_settings_export_rulesets_to_clipboard"
app:showAsAction="never" />
</menu> </menu>
@@ -29,12 +29,6 @@
<copyright>Copyright 2010-2016 JetBrains s.r.o.</copyright> <copyright>Copyright 2010-2016 JetBrains s.r.o.</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice>
<name>Logger</name>
<url>https://github.com/orhanobut/logger</url>
<copyright>Copyright 2015 Orhan Obut</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice> <notice>
<name>LeakCanary</name> <name>LeakCanary</name>
<url>https://github.com/square/leakcanary</url> <url>https://github.com/square/leakcanary</url>
@@ -32,6 +32,7 @@
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Type manually[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">الكتابة يدويًا [Trojan]</string> <string name="menu_item_import_config_manually_trojan">الكتابة يدويًا [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">الكتابة يدويًا [Wireguard]</string> <string name="menu_item_import_config_manually_wireguard">الكتابة يدويًا [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">تكوين مخصص</string> <string name="menu_item_import_config_custom">تكوين مخصص</string>
<string name="menu_item_import_config_custom_clipboard">استيراد تكوين مخصص من الحافظة</string> <string name="menu_item_import_config_custom_clipboard">استيراد تكوين مخصص من الحافظة</string>
<string name="menu_item_import_config_custom_local">استيراد تكوين مخصص من الجهاز</string> <string name="menu_item_import_config_custom_local">استيراد تكوين مخصص من الجهاز</string>
@@ -111,6 +112,7 @@
<string name="msg_file_not_found">الملف غير موجود</string> <string name="msg_file_not_found">الملف غير موجود</string>
<string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string> <string name="msg_remark_is_duplicate">الملاحظات موجودة بالفعل</string>
<string name="toast_action_not_allowed">الإجراء غير مسموح به</string> <string name="toast_action_not_allowed">الإجراء غير مسموح به</string>
<string name="server_obfs_password">Obfs password</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">جار التحميل</string> <string name="msg_dialog_progress">جار التحميل</string>
@@ -130,6 +132,8 @@
<string name="title_vpn_settings">إعدادات VPN</string> <string name="title_vpn_settings">إعدادات VPN</string>
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string> <string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string> <string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_mux_settings">إعدادات Mux</string> <string name="title_mux_settings">إعدادات Mux</string>
<string name="title_pref_mux_enabled">تمكين Mux</string> <string name="title_pref_mux_enabled">تمكين Mux</string>
@@ -230,6 +234,7 @@
<string name="title_sub_setting">إعداد مجموعة الاشتراك</string> <string name="title_sub_setting">إعداد مجموعة الاشتراك</string>
<string name="sub_setting_remarks">ملاحظات</string> <string name="sub_setting_remarks">ملاحظات</string>
<string name="sub_setting_url">عنوان URL اختياري</string> <string name="sub_setting_url">عنوان URL اختياري</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">تفعيل التحديث</string> <string name="sub_setting_enable">تفعيل التحديث</string>
<string name="sub_auto_update">تفعيل التحديث التلقائي</string> <string name="sub_auto_update">تفعيل التحديث التلقائي</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string> <string name="sub_setting_pre_profile">Previous proxy remarks</string>
@@ -256,6 +261,9 @@
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_rulesets">Import ruleset</string> <string name="routing_settings_import_rulesets">Import ruleset</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> <string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
<string name="connection_test_pending">التحقق من الاتصال</string> <string name="connection_test_pending">التحقق من الاتصال</string>
<string name="connection_test_testing">يجري الاختبار…</string> <string name="connection_test_testing">يجري الاختبار…</string>
@@ -32,6 +32,7 @@
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Type manually[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">ম্যানুয়ালি টাইপ করুন [Trojan]</string> <string name="menu_item_import_config_manually_trojan">ম্যানুয়ালি টাইপ করুন [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">ম্যানুয়ালি টাইপ করুন [Wireguard]</string> <string name="menu_item_import_config_manually_wireguard">ম্যানুয়ালি টাইপ করুন [Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">কাস্টম কনফিগারেশন</string> <string name="menu_item_import_config_custom">কাস্টম কনফিগারেশন</string>
<string name="menu_item_import_config_custom_clipboard">ক্লিপবোর্ড থেকে কাস্টম কনফিগারেশন আমদানি করুন</string> <string name="menu_item_import_config_custom_clipboard">ক্লিপবোর্ড থেকে কাস্টম কনফিগারেশন আমদানি করুন</string>
<string name="menu_item_import_config_custom_local">স্থানীয়ভাবে কাস্টম কনফিগারেশন আমদানি করুন</string> <string name="menu_item_import_config_custom_local">স্থানীয়ভাবে কাস্টম কনফিগারেশন আমদানি করুন</string>
@@ -110,6 +111,8 @@
<string name="msg_file_not_found">ফাইল খুঁজে পাওয়া যায়নি</string> <string name="msg_file_not_found">ফাইল খুঁজে পাওয়া যায়নি</string>
<string name="msg_remark_is_duplicate">মন্তব্য ইতিমধ্যে বিদ্যমান</string> <string name="msg_remark_is_duplicate">মন্তব্য ইতিমধ্যে বিদ্যমান</string>
<string name="toast_action_not_allowed">অ্যাকশন অনুমোদিত নয়</string> <string name="toast_action_not_allowed">অ্যাকশন অনুমোদিত নয়</string>
<string name="server_obfs_password">Obfs password</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">লোড হচ্ছে</string> <string name="msg_dialog_progress">লোড হচ্ছে</string>
<string name="menu_item_search">অনুসন্ধান করুন</string> <string name="menu_item_search">অনুসন্ধান করুন</string>
@@ -127,6 +130,8 @@
<string name="title_vpn_settings">VPN সেটিংস</string> <string name="title_vpn_settings">VPN সেটিংস</string>
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string> <string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string> <string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_mux_settings">Mux সেটিংস</string> <string name="title_mux_settings">Mux সেটিংস</string>
<string name="title_pref_mux_enabled">Mux সক্রিয় করুন</string> <string name="title_pref_mux_enabled">Mux সক্রিয় করুন</string>
@@ -228,6 +233,7 @@
<string name="title_sub_setting">সাবস্ক্রিপশন গ্রুপ সেটিং</string> <string name="title_sub_setting">সাবস্ক্রিপশন গ্রুপ সেটিং</string>
<string name="sub_setting_remarks">মন্তব্য</string> <string name="sub_setting_remarks">মন্তব্য</string>
<string name="sub_setting_url">ঐচ্ছিক URL</string> <string name="sub_setting_url">ঐচ্ছিক URL</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">আপডেট সক্রিয় করুন</string> <string name="sub_setting_enable">আপডেট সক্রিয় করুন</string>
<string name="sub_auto_update">স্বয়ংক্রিয় আপডেট সক্রিয় করুন</string> <string name="sub_auto_update">স্বয়ংক্রিয় আপডেট সক্রিয় করুন</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string> <string name="sub_setting_pre_profile">Previous proxy remarks</string>
@@ -253,6 +259,9 @@
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_rulesets">Import ruleset</string> <string name="routing_settings_import_rulesets">Import ruleset</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> <string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
<string name="connection_test_pending">সংযোগ পরীক্ষা করুন</string> <string name="connection_test_pending">সংযোগ পরীক্ষা করুন</string>
<string name="connection_test_testing">পরীক্ষা চলছে…</string> <string name="connection_test_testing">পরীক্ষা চলছে…</string>
+30 -15
View File
@@ -31,6 +31,7 @@
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Type manually[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">تایپ دستی[Trojan]</string> <string name="menu_item_import_config_manually_trojan">تایپ دستی[Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">[Wireguard]تایپ دستی</string> <string name="menu_item_import_config_manually_wireguard">[Wireguard]تایپ دستی</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">کانفیگ سفارشی</string> <string name="menu_item_import_config_custom">کانفیگ سفارشی</string>
<string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشی را از کلیپ‌بورد وارد کنید</string> <string name="menu_item_import_config_custom_clipboard">کانفیگ سفارشی را از کلیپ‌بورد وارد کنید</string>
<string name="menu_item_import_config_custom_local">کانفیگ سفارشی را به صورت محلی وارد کنید</string> <string name="menu_item_import_config_custom_local">کانفیگ سفارشی را به صورت محلی وارد کنید</string>
@@ -104,6 +105,7 @@
<string name="title_url">URL</string> <string name="title_url">URL</string>
<string name="menu_item_download_file">دانلود فایل‌ها</string> <string name="menu_item_download_file">دانلود فایل‌ها</string>
<string name="toast_action_not_allowed">این عمل ممنوع است</string> <string name="toast_action_not_allowed">این عمل ممنوع است</string>
<string name="server_obfs_password">Obfs password</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">URL را اضافه کنید</string> <string name="title_user_asset_add_url">URL را اضافه کنید</string>
@@ -125,6 +127,8 @@
<string name="title_vpn_settings">تنظیمات VPN</string> <string name="title_vpn_settings">تنظیمات VPN</string>
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string> <string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
<string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینه‌ای برای انتخاب خودکار پروکسی برنامه در منو است</string> <string name="summary_pref_per_app_proxy">عمومی: برنامه بررسی شده پروکسی است، اتصال مستقیم بدون بررسی است. \nحالت bypass: برنامه بررسی شده مستقیما متصل است، پراکسی بررسی نشده است. \nگزینه‌ای برای انتخاب خودکار پروکسی برنامه در منو است</string>
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد</string>
<string name="title_mux_settings">تنظیمات Mux</string> <string name="title_mux_settings">تنظیمات Mux</string>
<string name="title_pref_mux_enabled">فعال کردن Mux</string> <string name="title_pref_mux_enabled">فعال کردن Mux</string>
@@ -143,8 +147,8 @@
<string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string> <string name="title_pref_sniffing_enabled">فعال کردن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیش‌فرض روشن)</string> <string name="summary_pref_sniffing_enabled">دامنه sniff را از بسته امتحان کنید (پیش‌فرض روشن)</string>
<string name="title_pref_route_only_enabled">Enable routeOnly</string> <string name="title_pref_route_only_enabled">فعال کردن routeOnly</string>
<string name="summary_pref_route_only_enabled">Use the sniffed domain name for routing only, and keep the target address as the IP address.</string> <string name="summary_pref_route_only_enabled">از نام دامنه sniffed فقط برای مسیریابی استفاده کنید و آدرس مورد نظر را به عنوان آدرس IP نگه دارید.</string>
<string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string> <string name="title_pref_local_dns_enabled">فعال کردن DNS محلی</string>
@@ -171,8 +175,8 @@
<string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند</string> <string name="summary_pref_proxy_sharing_enabled">دستگاه‌های دیگر می‌توانند از طریق socks/http به پراکسی توسط نشانی آی‌پی شما متصل شوند، فقط در شبکه مورد اعتماد فعال می‌شوند تا از اتصال غیرمجاز جلوگیری کنند</string>
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string> <string name="toast_warning_pref_proxysharing_short">اتصالات از طریق LAN را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید</string>
<string name="title_pref_allow_insecure">allowInsecure</string> <string name="title_pref_allow_insecure">موز ناامن</string>
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض allowInsecure فعال است</string> <string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌فرض مجوز ناامن فعال است</string>
<string name="title_pref_socks_port">پورت پروکسی SOCKS5</string> <string name="title_pref_socks_port">پورت پروکسی SOCKS5</string>
<string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string> <string name="summary_pref_socks_port">پورت پروکسی SOCKS5</string>
@@ -196,12 +200,12 @@
<string name="title_privacy_policy">حریم خصوصی</string> <string name="title_privacy_policy">حریم خصوصی</string>
<string name="title_about">درباره</string> <string name="title_about">درباره</string>
<string name="title_source_code">Source code</string> <string name="title_source_code">کد منبع</string>
<string name="title_tg_channel">Telegram channel</string> <string name="title_tg_channel">کانال تلگرام</string>
<string name="title_configuration_backup">Backup configuration</string> <string name="title_configuration_backup">پشتیبانگیری از پیکربندی</string>
<string name="summary_configuration_backup">Storage location: [%s], The backup will be cleared after uninstalling the app or clearing the storage</string> <string name="summary_configuration_backup">محل ذخیره سازی: [%s], پس از حذف نصب برنامه یا پاک کردن فضای ذخیره سازی، نسخه پشتیبان پاک می شود</string>
<string name="title_configuration_restore">Restore configuration</string> <string name="title_configuration_restore">بازیابی پیکربندی</string>
<string name="title_configuration_share">Share configuration</string> <string name="title_configuration_share">اشتراکگذاری پیکربندی</string>
<string name="title_pref_promotion">تبلیغات</string> <string name="title_pref_promotion">تبلیغات</string>
<string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string> <string name="summary_pref_promotion">تبلیغات، برای جزئیات بیشتر کلیک کنید (کمک مالی کنید تا حذف شود)</string>
@@ -226,6 +230,7 @@
<string name="title_sub_setting">تنظیمات گروه‌ی اشتراک</string> <string name="title_sub_setting">تنظیمات گروه‌ی اشتراک</string>
<string name="sub_setting_remarks">ملاحظات</string> <string name="sub_setting_remarks">ملاحظات</string>
<string name="sub_setting_url">نشانی اینترنتی اختیاری</string> <string name="sub_setting_url">نشانی اینترنتی اختیاری</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">فعال کردن به‌روزرسانی</string> <string name="sub_setting_enable">فعال کردن به‌روزرسانی</string>
<string name="sub_auto_update">فعال سازی به‌روزرسانی خودکار</string> <string name="sub_auto_update">فعال سازی به‌روزرسانی خودکار</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string> <string name="sub_setting_pre_profile">Previous proxy remarks</string>
@@ -247,11 +252,14 @@
<string name="routing_settings_title">تنظیمات مسیریابی</string> <string name="routing_settings_title">تنظیمات مسیریابی</string>
<string name="routing_settings_tips">با کاما (,) از هم جدا شوند، ذخیره کردن فراموش نشود</string> <string name="routing_settings_tips">با کاما (,) از هم جدا شوند، ذخیره کردن فراموش نشود</string>
<string name="routing_settings_save">ذخیره</string> <string name="routing_settings_save">ذخیره</string>
<string name="routing_settings_delete">پاک کردن</string> <string name="routing_settings_delete">حذف</string>
<string name="routing_settings_rule_title">Routing Rule Settings</string> <string name="routing_settings_rule_title">تنظیمات قانون مسیریابی</string>
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">اضافه کردن قانون</string>
<string name="routing_settings_import_rulesets">Import ruleset</string> <string name="routing_settings_import_rulesets">وارد کردن مجموعه قوانین</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> <string name="routing_settings_import_rulesets_tip">مجموعه قوانین موجود حذف خواهند شد، آیا مطمئن هستید که ادامه می دهید؟</string>
<string name="routing_settings_import_rulesets_from_clipboard">وارد کردن مجموعه قوانین از کلیپ بورد</string>
<string name="routing_settings_export_rulesets_to_clipboard">صادر کردن مجموعه قوانین به کلیپ بورد</string>
<string name="routing_settings_locked">قفل است، این قانون را هنگام وارد کردن از پیش تنظیم‌ها حفظ کنید</string>
<string name="connection_test_pending">اتصال را بررسی کنید</string> <string name="connection_test_pending">اتصال را بررسی کنید</string>
<string name="connection_test_testing">در حال آزمایش...</string> <string name="connection_test_testing">در حال آزمایش...</string>
@@ -293,4 +301,11 @@
<item>Dark</item> <item>Dark</item>
</string-array> </string-array>
<string-array name="preset_rulesets">
<item>لیست سفید چین</item>
<item>لیست سیاه چین</item>
<item>جهانی(Global)</item>
<item>ایران</item>
</string-array>
</resources> </resources>
+30 -15
View File
@@ -24,13 +24,14 @@
<string name="menu_item_del_config">Удалить профиль</string> <string name="menu_item_del_config">Удалить профиль</string>
<string name="menu_item_import_config_qrcode">Импорт из QR-кода</string> <string name="menu_item_import_config_qrcode">Импорт из QR-кода</string>
<string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string> <string name="menu_item_import_config_clipboard">Импорт из буфера обмена</string>
<string name="menu_item_import_config_manually_vmess">Ручной ввод Vmess</string> <string name="menu_item_import_config_manually_vmess">Ручной ввод VMess</string>
<string name="menu_item_import_config_manually_vless">Ручной ввод VLESS</string> <string name="menu_item_import_config_manually_vless">Ручной ввод VLESS</string>
<string name="menu_item_import_config_manually_ss">Ручной ввод Shadowsocks</string> <string name="menu_item_import_config_manually_ss">Ручной ввод Shadowsocks</string>
<string name="menu_item_import_config_manually_socks">Ручной ввод Socks</string> <string name="menu_item_import_config_manually_socks">Ручной ввод SOCKS</string>
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Ручной ввод HTTP</string>
<string name="menu_item_import_config_manually_trojan">Ручной ввод Trojan</string> <string name="menu_item_import_config_manually_trojan">Ручной ввод Trojan</string>
<string name="menu_item_import_config_manually_wireguard">Ручной ввод Wireguard</string> <string name="menu_item_import_config_manually_wireguard">Ручной ввод WireGuard</string>
<string name="menu_item_import_config_manually_hysteria2">Ручной ввод Hysteria2</string>
<string name="menu_item_import_config_custom">Другой профиль</string> <string name="menu_item_import_config_custom">Другой профиль</string>
<string name="menu_item_import_config_custom_clipboard">Импорт из буфера обмена</string> <string name="menu_item_import_config_custom_clipboard">Импорт из буфера обмена</string>
<string name="menu_item_import_config_custom_local">Импорт из файла</string> <string name="menu_item_import_config_custom_local">Импорт из файла</string>
@@ -38,14 +39,14 @@
<string name="menu_item_import_config_custom_url_scan">Импорт сканированием URL</string> <string name="menu_item_import_config_custom_url_scan">Импорт сканированием URL</string>
<string name="del_config_comfirm">Подтверждаете удаление?</string> <string name="del_config_comfirm">Подтверждаете удаление?</string>
<string name="del_invalid_config_comfirm">Выполните проверку перед удалением! Подтверждаете удаление?</string> <string name="del_invalid_config_comfirm">Выполните проверку перед удалением! Подтверждаете удаление?</string>
<string name="server_lab_remarks">Описание</string> <string name="server_lab_remarks">Название</string>
<string name="server_lab_address">Адрес</string> <string name="server_lab_address">Адрес</string>
<string name="server_lab_port">Порт</string> <string name="server_lab_port">Порт</string>
<string name="server_lab_id">ID</string> <string name="server_lab_id">ID</string>
<string name="server_lab_alterid">Альтернативный ID</string> <string name="server_lab_alterid">Альтернативный ID</string>
<string name="server_lab_security">Безопасность</string> <string name="server_lab_security">Безопасность</string>
<string name="server_lab_network">Сеть</string> <string name="server_lab_network">Сеть</string>
<string name="server_lab_more_function">Другие параметры</string> <string name="server_lab_more_function">Транспорт</string>
<string name="server_lab_head_type">Тип заголовка</string> <string name="server_lab_head_type">Тип заголовка</string>
<string name="server_lab_mode_type">Режим gRPC</string> <string name="server_lab_mode_type">Режим gRPC</string>
<string name="server_lab_request_host">Узел</string> <string name="server_lab_request_host">Узел</string>
@@ -109,7 +110,7 @@
<string name="msg_file_not_found">Файл не найден</string> <string name="msg_file_not_found">Файл не найден</string>
<string name="msg_remark_is_duplicate">Название уже существует</string> <string name="msg_remark_is_duplicate">Название уже существует</string>
<string name="toast_action_not_allowed">Это действие запрещено</string> <string name="toast_action_not_allowed">Это действие запрещено</string>
<string name="server_obfs_password">Пароль obfs</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Загрузка…</string> <string name="msg_dialog_progress">Загрузка…</string>
@@ -128,7 +129,9 @@
<string name="title_advanced">Расширенные настройки</string> <string name="title_advanced">Расширенные настройки</string>
<string name="title_vpn_settings">Настройки VPN</string> <string name="title_vpn_settings">Настройки VPN</string>
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string> <string name="title_pref_per_app_proxy">Прокси для выбранных приложений</string>
<string name="summary_pref_per_app_proxy">Основной: выделенное приложение соединяется через прокси, не выделенное — напрямую;\n\nРежим обхода: выделенное приложение соединяется напрямую, не выделенное — через прокси.\n\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string> <string name="summary_pref_per_app_proxy">Основной: выделенное приложение соединяется через прокси, не выделенное — напрямую;\nРежим обхода: выделенное приложение соединяется напрямую, не выделенное — через прокси.\nЕсть возможность автоматического выбора проксируемых приложений в меню.</string>
<string name="title_pref_is_booted">Автоподключение при запуске</string>
<string name="summary_pref_is_booted">Автоматически подключаться к выбранному серверу при запуске приложения (может оказаться неудачным)</string>
<string name="title_mux_settings">Настройки мультиплексирования</string> <string name="title_mux_settings">Настройки мультиплексирования</string>
<string name="title_pref_mux_enabled">Использовать мультиплексирование</string> <string name="title_pref_mux_enabled">Использовать мультиплексирование</string>
@@ -148,7 +151,7 @@
<string name="title_pref_sniffing_enabled">Анализ пакетов</string> <string name="title_pref_sniffing_enabled">Анализ пакетов</string>
<string name="summary_pref_sniffing_enabled">Использовать определение доменных имён в пакетах (по умолчанию включено)</string> <string name="summary_pref_sniffing_enabled">Использовать определение доменных имён в пакетах (по умолчанию включено)</string>
<string name="title_pref_route_only_enabled">Домен только для маршрутизации</string> <string name="title_pref_route_only_enabled">Домен только для маршрутизации</string>
<string name="summary_pref_route_only_enabled">Использовать определённое доменное имя только для маршрутизации и сохранять целевой адрес в виде IP.</string> <string name="summary_pref_route_only_enabled">Использовать доменное имя только для маршрутизации и сохранять целевой адрес в виде IP.</string>
<string name="title_pref_local_dns_enabled">Использовать локальную DNS</string> <string name="title_pref_local_dns_enabled">Использовать локальную DNS</string>
<string name="summary_pref_local_dns_enabled">Обслуживание выполняется DNS-модулем ядра (в настройках маршрутизации рекомендуется выбрать режим «Все, кроме LAN и Китая»)</string> <string name="summary_pref_local_dns_enabled">Обслуживание выполняется DNS-модулем ядра (в настройках маршрутизации рекомендуется выбрать режим «Все, кроме LAN и Китая»)</string>
@@ -172,7 +175,7 @@
<string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string> <string name="title_pref_proxy_sharing_enabled">Разрешать подключения из LAN</string>
<string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать прокси по протоколам SOCKS/HTTP. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string> <string name="summary_pref_proxy_sharing_enabled">Другие устройства могут подключаться, используя ваш IP-адрес, чтобы использовать прокси по протоколам SOCKS/HTTP. Используйте только в доверенной сети, чтобы избежать несанкционированного подключения.</string>
<string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в надёжной сети</string> <string name="toast_warning_pref_proxysharing_short">Доступ из LAN разрешён, убедитесь, что вы находитесь в доверенной сети</string>
<string name="title_pref_allow_insecure">Разрешать небезопасные</string> <string name="title_pref_allow_insecure">Разрешать небезопасные</string>
<string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string> <string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string>
@@ -230,11 +233,12 @@
<string name="title_sub_setting">Группы</string> <string name="title_sub_setting">Группы</string>
<string name="sub_setting_remarks">Название</string> <string name="sub_setting_remarks">Название</string>
<string name="sub_setting_url">URL (необязательно)</string> <string name="sub_setting_url">URL (необязательно)</string>
<string name="sub_setting_filter">Название фильтра</string>
<string name="sub_setting_enable">Использовать обновление</string> <string name="sub_setting_enable">Использовать обновление</string>
<string name="sub_auto_update">Использовать автоматическое обновление</string> <string name="sub_auto_update">Использовать автообновление</string>
<string name="sub_setting_pre_profile">Название предыдущего прокси</string> <string name="sub_setting_pre_profile">Название предыдущего прокси</string>
<string name="sub_setting_next_profile">Название следующего прокси</string> <string name="sub_setting_next_profile">Название следующего прокси</string>
<string name="sub_setting_pre_profile_tip">Убедитесь, что название существует и является уникальным</string> <string name="sub_setting_pre_profile_tip">Название должно существовать и быть уникальным</string>
<string name="title_sub_update">Обновить подписку группы</string> <string name="title_sub_update">Обновить подписку группы</string>
<string name="title_ping_all_server">Проверка профилей группы</string> <string name="title_ping_all_server">Проверка профилей группы</string>
<string name="title_real_ping_all_server">Время отклика профилей группы</string> <string name="title_real_ping_all_server">Время отклика профилей группы</string>
@@ -248,14 +252,25 @@
<string name="tasker_setting_confirm">Подтвердить</string> <string name="tasker_setting_confirm">Подтвердить</string>
<string name="routing_settings_domain_strategy">Доменная стратегия</string> <string name="routing_settings_domain_strategy">Доменная стратегия</string>
<string name="routing_settings_title">Настройки маршрутизации</string> <string name="routing_settings_title">Маршрутизация</string>
<string name="routing_settings_tips">Введите требуемые IP/URL через запятую. Не забудьте сохранить изменения.</string> <string name="routing_settings_tips">Введите требуемые домены/IP через запятую</string>
<string name="routing_settings_save">Сохранить</string> <string name="routing_settings_save">Сохранить</string>
<string name="routing_settings_delete">Очистить</string> <string name="routing_settings_delete">Очистить</string>
<string name="routing_settings_rule_title">Настройка правил маршрутизации</string> <string name="routing_settings_rule_title">Настройка правил маршрутизации</string>
<string name="routing_settings_add_rule">Добавить правило</string> <string name="routing_settings_add_rule">Добавить правило</string>
<string name="routing_settings_import_rulesets">Импорт правил</string> <string name="routing_settings_import_rulesets">Импорт правил</string>
<string name="routing_settings_import_rulesets_tip">Существующие правила будут удалены. Продолжить?</string> <string name="routing_settings_import_rulesets_tip">Существующие правила будут удалены. Продолжить?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Импорт правил из буфера обмена</string>
<string name="routing_settings_export_rulesets_to_clipboard">Экспорт правил в буфер обмена</string>
<string name="routing_settings_locked">Постоянное (сохранится при импорте правил)</string>
<string name="routing_settings_domain">Домен</string>
<string name="routing_settings_ip">IP</string>
<string name="routing_settings_port">Порт</string>
<string name="routing_settings_protocol">Протокол</string>
<string name="routing_settings_protocol_tip">[http,tls,bittorrent]</string>
<string name="routing_settings_network">Сеть</string>"
<string name="routing_settings_network_tip">[udp|tcp]</string>
<string name="routing_settings_outbound_tag">Исходящее подключение</string>
<string name="connection_test_pending">Проверить подключение</string> <string name="connection_test_pending">Проверить подключение</string>
<string name="connection_test_testing">Проверка…</string> <string name="connection_test_testing">Проверка…</string>
@@ -287,7 +302,7 @@
<string-array name="mode_entries"> <string-array name="mode_entries">
<item>VPN</item> <item>VPN</item>
<item>Только прокси</item> <item>Прокси</item>
</string-array> </string-array>
<string-array name="ui_mode_night"> <string-array name="ui_mode_night">
@@ -31,6 +31,7 @@
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Type manually[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">Nhập thủ công [Trojan]</string> <string name="menu_item_import_config_manually_trojan">Nhập thủ công [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">Nhập thủ công [WireGuard]</string> <string name="menu_item_import_config_manually_wireguard">Nhập thủ công [WireGuard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">Nâng cao / Cấu hình tùy chỉnh</string> <string name="menu_item_import_config_custom">Nâng cao / Cấu hình tùy chỉnh</string>
<string name="menu_item_import_config_custom_clipboard">Nhập cấu hình tùy chỉnh từ Clipboard</string> <string name="menu_item_import_config_custom_clipboard">Nhập cấu hình tùy chỉnh từ Clipboard</string>
<string name="menu_item_import_config_custom_local">Nhập cấu hình tùy chỉnh từ Tệp</string> <string name="menu_item_import_config_custom_local">Nhập cấu hình tùy chỉnh từ Tệp</string>
@@ -104,6 +105,7 @@
<string name="title_url">URL</string> <string name="title_url">URL</string>
<string name="menu_item_download_file">Tải xuống tệp tin</string> <string name="menu_item_download_file">Tải xuống tệp tin</string>
<string name="toast_action_not_allowed">Hành động này bị cấm!</string> <string name="toast_action_not_allowed">Hành động này bị cấm!</string>
<string name="server_obfs_password">Obfs password</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">Thêm URL nội dung</string> <string name="title_user_asset_add_url">Thêm URL nội dung</string>
@@ -126,6 +128,8 @@
<string name="title_vpn_settings">Cài đặt VPN</string> <string name="title_vpn_settings">Cài đặt VPN</string>
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string> <string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string> <string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_mux_settings">Cài đặt Mux</string> <string name="title_mux_settings">Cài đặt Mux</string>
<string name="title_pref_mux_enabled">Bật Mux</string> <string name="title_pref_mux_enabled">Bật Mux</string>
@@ -229,6 +233,7 @@
<string name="title_sub_setting">Các gói đăng ký</string> <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_remarks">Tên gói đăng ký</string>
<string name="sub_setting_url">URL gói đăng ký</string> <string name="sub_setting_url">URL gói đăng ký</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_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> <string name="sub_auto_update">Bật tự động cập nhật</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string> <string name="sub_setting_pre_profile">Previous proxy remarks</string>
@@ -255,6 +260,9 @@
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_rulesets">Import ruleset</string> <string name="routing_settings_import_rulesets">Import ruleset</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> <string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
<string name="connection_test_pending">Kiểm tra kết nối</string> <string name="connection_test_pending">Kiểm tra kết nối</string>
<string name="connection_test_testing">Đang kiểm tra kết nối mạng...</string> <string name="connection_test_testing">Đang kiểm tra kết nối mạng...</string>
@@ -31,6 +31,7 @@
<string name="menu_item_import_config_manually_http">手动输入[HTTP]</string> <string name="menu_item_import_config_manually_http">手动输入[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">手动输入[Trojan]</string> <string name="menu_item_import_config_manually_trojan">手动输入[Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">手动输入[Wireguard]</string> <string name="menu_item_import_config_manually_wireguard">手动输入[Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">手动输入[Hysteria2]</string>
<string name="menu_item_import_config_custom">自定义配置</string> <string name="menu_item_import_config_custom">自定义配置</string>
<string name="menu_item_import_config_custom_clipboard">从剪贴板导入自定义配置</string> <string name="menu_item_import_config_custom_clipboard">从剪贴板导入自定义配置</string>
<string name="menu_item_import_config_custom_local">从本地导入自定义配置</string> <string name="menu_item_import_config_custom_local">从本地导入自定义配置</string>
@@ -104,7 +105,7 @@
<string name="title_url">URL</string> <string name="title_url">URL</string>
<string name="menu_item_download_file">下载文件</string> <string name="menu_item_download_file">下载文件</string>
<string name="toast_action_not_allowed">禁止此项操作</string> <string name="toast_action_not_allowed">禁止此项操作</string>
<string name="server_obfs_password">混淆密码</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">添加资产网址</string> <string name="title_user_asset_add_url">添加资产网址</string>
@@ -126,6 +127,8 @@
<string name="title_vpn_settings">VPN 设置</string> <string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string> <string name="title_pref_per_app_proxy">分应用代理</string>
<string name="summary_pref_per_app_proxy">常规:勾选的App被代理,未勾选的直连;\n绕行模式:勾选的App直连,未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string> <string name="summary_pref_per_app_proxy">常规:勾选的App被代理,未勾选的直连;\n绕行模式:勾选的App直连,未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
<string name="title_pref_is_booted">开机时自动连接</string>
<string name="summary_pref_is_booted">开机时自动连接选择的服务器,可能会不成功</string>
<string name="title_mux_settings">Mux 多路复用 设置</string> <string name="title_mux_settings">Mux 多路复用 设置</string>
<string name="title_pref_mux_enabled">启用 Mux 多路复用</string> <string name="title_pref_mux_enabled">启用 Mux 多路复用</string>
@@ -227,6 +230,7 @@
<string name="title_sub_setting">订阅分组设置</string> <string name="title_sub_setting">订阅分组设置</string>
<string name="sub_setting_remarks">备注</string> <string name="sub_setting_remarks">备注</string>
<string name="sub_setting_url">可选地址(url)</string> <string name="sub_setting_url">可选地址(url)</string>
<string name="sub_setting_filter">别名正则过滤</string>
<string name="sub_setting_enable">启用更新</string> <string name="sub_setting_enable">启用更新</string>
<string name="sub_auto_update">启用自动更新</string> <string name="sub_auto_update">启用自动更新</string>
<string name="sub_setting_pre_profile">前置代理别名</string> <string name="sub_setting_pre_profile">前置代理别名</string>
@@ -246,13 +250,16 @@
<string name="routing_settings_domain_strategy">域名策略</string> <string name="routing_settings_domain_strategy">域名策略</string>
<string name="routing_settings_title">路由设置</string> <string name="routing_settings_title">路由设置</string>
<string name="routing_settings_tips">用逗号(,)隔开,可以一行多个</string> <string name="routing_settings_tips">用逗号(,)隔开,domain和ip二选一填写</string>
<string name="routing_settings_save">保存</string> <string name="routing_settings_save">保存</string>
<string name="routing_settings_delete">清空</string> <string name="routing_settings_delete">清空</string>
<string name="routing_settings_rule_title">路由规则设置</string> <string name="routing_settings_rule_title">路由规则设置</string>
<string name="routing_settings_add_rule">添加规则</string> <string name="routing_settings_add_rule">添加规则</string>
<string name="routing_settings_import_rulesets">导入预设规则集</string> <string name="routing_settings_import_rulesets">导入预设规则集</string>
<string name="routing_settings_import_rulesets_tip">将删除现有的规则集,是否确定继续?</string> <string name="routing_settings_import_rulesets_tip">将删除现有的规则集,是否确定继续?</string>
<string name="routing_settings_import_rulesets_from_clipboard">从剪贴板导入规则集</string>
<string name="routing_settings_export_rulesets_to_clipboard">导出规则集至剪贴板</string>
<string name="routing_settings_locked">锁定中,导入预设时不删除此规则</string>
<string name="connection_test_pending">"检查网络连接"</string> <string name="connection_test_pending">"检查网络连接"</string>
<string name="connection_test_testing">"测试中…"</string> <string name="connection_test_testing">"测试中…"</string>
@@ -299,6 +306,7 @@
<item>绕过大陆(Whitelist)</item> <item>绕过大陆(Whitelist)</item>
<item>黑名单(Blacklist)</item> <item>黑名单(Blacklist)</item>
<item>全局(Global)</item> <item>全局(Global)</item>
<item>伊朗(Iran)</item>
</string-array> </string-array>
</resources> </resources>
@@ -18,12 +18,12 @@
<string name="toast_services_failure">啟動服務失敗</string> <string name="toast_services_failure">啟動服務失敗</string>
<!--ServerActivity--> <!--ServerActivity-->
<string name="title_server">配置檔案</string> <string name="title_server">設定檔</string>
<string name="menu_item_add_config">新增配置</string> <string name="menu_item_add_config">新增設定</string>
<string name="menu_item_save_config">儲存配置</string> <string name="menu_item_save_config">儲存設定</string>
<string name="menu_item_del_config">刪除配置</string> <string name="menu_item_del_config">刪除設定</string>
<string name="menu_item_import_config_qrcode">從 QR Code 匯入配置</string> <string name="menu_item_import_config_qrcode">從 QR Code 匯入設定</string>
<string name="menu_item_import_config_clipboard">從剪貼簿匯入配置</string> <string name="menu_item_import_config_clipboard">從剪貼簿匯入設定</string>
<string name="menu_item_import_config_manually_vmess">手動鍵入 [VMess]</string> <string name="menu_item_import_config_manually_vmess">手動鍵入 [VMess]</string>
<string name="menu_item_import_config_manually_vless">手動鍵入 [VLESS]</string> <string name="menu_item_import_config_manually_vless">手動鍵入 [VLESS]</string>
<string name="menu_item_import_config_manually_ss">手動鍵入 [Shadowsocks]</string> <string name="menu_item_import_config_manually_ss">手動鍵入 [Shadowsocks]</string>
@@ -31,11 +31,12 @@
<string name="menu_item_import_config_manually_http">手動鍵入 [HTTP]</string> <string name="menu_item_import_config_manually_http">手動鍵入 [HTTP]</string>
<string name="menu_item_import_config_manually_trojan">手動鍵入 [Trojan]</string> <string name="menu_item_import_config_manually_trojan">手動鍵入 [Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">手動鍵入 [Wireguard]</string> <string name="menu_item_import_config_manually_wireguard">手動鍵入 [Wireguard]</string>
<string name="menu_item_import_config_custom">自訂配置</string> <string name="menu_item_import_config_manually_hysteria2">手動鍵入 [Hysteria2]</string>
<string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂配置</string> <string name="menu_item_import_config_custom">自訂設定</string>
<string name="menu_item_import_config_custom_local">從本地匯入自訂配置</string> <string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂設定</string>
<string name="menu_item_import_config_custom_url">從 URL 匯入自訂配置</string> <string name="menu_item_import_config_custom_local">從本地匯入自訂設定</string>
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂配置</string> <string name="menu_item_import_config_custom_url">從 URL 匯入自訂設定</string>
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂設定</string>
<string name="del_config_comfirm">確定刪除?</string> <string name="del_config_comfirm">確定刪除?</string>
<string name="del_invalid_config_comfirm">刪除前請先測試!確認刪除?</string> <string name="del_invalid_config_comfirm">刪除前請先測試!確認刪除?</string>
<string name="server_lab_remarks">備註</string> <string name="server_lab_remarks">備註</string>
@@ -89,21 +90,22 @@
<string name="toast_none_data">無資料</string> <string name="toast_none_data">無資料</string>
<string name="toast_incorrect_protocol">通訊協定不正確</string> <string name="toast_incorrect_protocol">通訊協定不正確</string>
<string name="toast_decoding_failed">解碼失敗</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="toast_require_file_manager">請安裝檔案總管。</string>
<string name="server_customize_config">自訂配置</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="server_lab_content">內容</string>
<string name="toast_none_data_clipboard">剪貼簿內無資料</string> <string name="toast_none_data_clipboard">剪貼簿內無資料</string>
<string name="toast_invalid_url">URL 無效</string> <string name="toast_invalid_url">URL 無效</string>
<string name="server_lab_need_inbound">​​確保 inbounds port 和設定中的一致</string> <string name="server_lab_need_inbound">​​確保 inbounds port 和設定中的一致</string>
<string name="toast_malformed_josn">配置格式不正確</string> <string name="toast_malformed_josn">設定格式不正確</string>
<string name="server_lab_request_host6">Host(SNI)(可選)</string> <string name="server_lab_request_host6">Host(SNI)(可選)</string>
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string> <string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
<string name="menu_item_add_file">新增檔案</string> <string name="menu_item_add_file">新增檔案</string>
<string name="title_url">URL</string> <string name="title_url">URL</string>
<string name="menu_item_download_file">下載檔案</string> <string name="menu_item_download_file">下載檔案</string>
<string name="toast_action_not_allowed">禁止此項操作</string> <string name="toast_action_not_allowed">禁止此項操作</string>
<string name="server_obfs_password">混淆密碼</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="title_user_asset_add_url">新增資產網址</string> <string name="title_user_asset_add_url">新增資產網址</string>
@@ -126,6 +128,8 @@
<string name="title_vpn_settings">VPN 設定</string> <string name="title_vpn_settings">VPN 設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string> <string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy,未勾選的直接連線;\n繞行模式:勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string> <string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy,未勾選的直接連線;\n繞行模式:勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
<string name="title_pref_is_booted">開機時自動連線</string>
<string name="summary_pref_is_booted">開機時自動連線選擇的伺服器,可能會不成功</string>
<string name="title_mux_settings">Mux 設定</string> <string name="title_mux_settings">Mux 設定</string>
<string name="title_pref_mux_enabled">啟用 Mux 多路復用</string> <string name="title_pref_mux_enabled">啟用 Mux 多路復用</string>
@@ -184,8 +188,8 @@
<string name="title_pref_local_dns_port">本機 DNS 埠</string> <string name="title_pref_local_dns_port">本機 DNS 埠</string>
<string name="summary_pref_local_dns_port">本機 DNS 埠</string> <string name="summary_pref_local_dns_port">本機 DNS 埠</string>
<string name="title_pref_confirm_remove">刪除配置檔案確認</string> <string name="title_pref_confirm_remove">刪除設定檔確認</string>
<string name="summary_pref_confirm_remove">刪除配置檔案是否需要用戶二次確認</string> <string name="summary_pref_confirm_remove">刪除設定檔是否需要用戶二次確認</string>
<string name="title_pref_start_scan_immediate">立即啟動掃碼</string> <string name="title_pref_start_scan_immediate">立即啟動掃碼</string>
<string name="summary_pref_start_scan_immediate">啟動時立即打開相機掃描,否則可在工具欄選擇掃碼或選照片</string> <string name="summary_pref_start_scan_immediate">啟動時立即打開相機掃描,否則可在工具欄選擇掃碼或選照片</string>
@@ -198,10 +202,10 @@
<string name="title_about">關於</string> <string name="title_about">關於</string>
<string name="title_source_code">原始碼</string> <string name="title_source_code">原始碼</string>
<string name="title_tg_channel">Telegram 頻道</string> <string name="title_tg_channel">Telegram 頻道</string>
<string name="title_configuration_backup">備份配置</string> <string name="title_configuration_backup">備份設定</string>
<string name="summary_configuration_backup">儲存位置: [%s], 卸載App或清除儲存後備份將被清除</string> <string name="summary_configuration_backup">儲存位置: [%s], 卸載App或清除儲存後備份將被清除</string>
<string name="title_configuration_restore">還原配置</string> <string name="title_configuration_restore">還原設定</string>
<string name="title_configuration_share">分享配置</string> <string name="title_configuration_share">分享設定</string>
<string name="title_pref_promotion">推廣</string> <string name="title_pref_promotion">推廣</string>
<string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string> <string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string>
@@ -221,24 +225,25 @@
<string name="logcat_copy">複製</string> <string name="logcat_copy">複製</string>
<string name="logcat_clear">清除</string> <string name="logcat_clear">清除</string>
<string name="title_service_restart">重啟服務</string> <string name="title_service_restart">重啟服務</string>
<string name="title_del_all_config">刪除目前群組配置</string> <string name="title_del_all_config">刪除目前群組設定</string>
<string name="title_del_duplicate_config">刪除目前群組重複配置</string> <string name="title_del_duplicate_config">刪除目前群組重複設定</string>
<string name="title_del_invalid_config">刪除目前群組無效配置</string> <string name="title_del_invalid_config">刪除目前群組無效設定</string>
<string name="title_export_all">匯出目前群組配置至剪貼簿</string> <string name="title_export_all">匯出目前群組設定至剪貼簿</string>
<string name="title_sub_setting">訂閱分組設定</string> <string name="title_sub_setting">訂閱分組設定</string>
<string name="sub_setting_remarks">備註</string> <string name="sub_setting_remarks">備註</string>
<string name="sub_setting_url">Optional URL</string> <string name="sub_setting_url">可選位址(url)</string>
<string name="sub_setting_filter">別名正規過濾</string>
<string name="sub_setting_enable">啟用更新</string> <string name="sub_setting_enable">啟用更新</string>
<string name="sub_auto_update">啟用自動更新</string> <string name="sub_auto_update">啟用自動更新</string>
<string name="sub_setting_pre_profile">前置代理别名</string> <string name="sub_setting_pre_profile">前置代理别名</string>
<string name="sub_setting_next_profile">落地代理別名</string> <string name="sub_setting_next_profile">落地代理別名</string>
<string name="sub_setting_pre_profile_tip">请确保别名存在并唯一</string> <string name="sub_setting_pre_profile_tip">请确保别名存在并唯一</string>
<string name="title_sub_update">更新目前群組訂閱</string> <string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組配置 Tcping</string> <string name="title_ping_all_server">偵測目前群組設定 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組配置真延遲</string> <string name="title_real_ping_all_server">偵測目前群組設定真延遲</string>
<string name="title_user_asset_setting">Geo 資源檔案</string> <string name="title_user_asset_setting">Geo 資源檔案</string>
<string name="title_sort_by_test_results">依偵測結果排序</string> <string name="title_sort_by_test_results">依偵測結果排序</string>
<string name="title_filter_config">過濾配置</string> <string name="title_filter_config">過濾設定</string>
<string name="filter_config_all">所有分組</string> <string name="filter_config_all">所有分組</string>
<string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string> <string name="title_del_duplicate_config_count">Delete %d duplicate configurations</string>
@@ -247,13 +252,16 @@
<string name="routing_settings_domain_strategy">網域策略</string> <string name="routing_settings_domain_strategy">網域策略</string>
<string name="routing_settings_title">轉送設定</string> <string name="routing_settings_title">轉送設定</string>
<string name="routing_settings_tips">以半形逗號「,」分隔</string> <string name="routing_settings_tips">以半形逗號「,」分隔,domain和ip二選一填寫</string>
<string name="routing_settings_save">儲存</string> <string name="routing_settings_save">儲存</string>
<string name="routing_settings_delete">清除</string> <string name="routing_settings_delete">清除</string>
<string name="routing_settings_rule_title">路由規則設定</string> <string name="routing_settings_rule_title">路由規則設定</string>
<string name="routing_settings_add_rule">新增規則</string> <string name="routing_settings_add_rule">新增規則</string>
<string name="routing_settings_import_rulesets">匯入預設規則集</string> <string name="routing_settings_import_rulesets">匯入預設規則集</string>
<string name="routing_settings_import_rulesets_tip">將刪除現有的規則集,是否確定繼續? </string> <string name="routing_settings_import_rulesets_tip">將刪除現有的規則集,是否確定繼續? </string>
<string name="routing_settings_import_rulesets_from_clipboard">從剪貼簿匯入規則集</string>
<string name="routing_settings_export_rulesets_to_clipboard">匯出規則集至剪貼簿</string>
<string name="routing_settings_locked">鎖定中,匯入預設時不刪除此規則</string>
<string name="connection_test_pending">"測試連線能力"</string> <string name="connection_test_pending">"測試連線能力"</string>
<string name="connection_test_testing">"測試中……"</string> <string name="connection_test_testing">"測試中……"</string>
@@ -277,7 +285,7 @@
<string-array name="share_method"> <string-array name="share_method">
<item>QR Code</item> <item>QR Code</item>
<item>匯出至剪貼簿</item> <item>匯出至剪貼簿</item>
<item>匯出完整配置至剪貼簿</item> <item>匯出完整設定至剪貼簿</item>
</string-array> </string-array>
<string-array name="share_sub_method"> <string-array name="share_sub_method">
@@ -300,6 +308,7 @@
<item>繞過大陸(Whitelist)</item> <item>繞過大陸(Whitelist)</item>
<item>黑名單(Blacklist)</item> <item>黑名單(Blacklist)</item>
<item>全域(Global)</item> <item>全域(Global)</item>
<item>伊朗(Iran)</item>
</string-array> </string-array>
</resources> </resources>
+10 -2
View File
@@ -32,6 +32,7 @@
<string name="menu_item_import_config_manually_http">Type manually[HTTP]</string> <string name="menu_item_import_config_manually_http">Type manually[HTTP]</string>
<string name="menu_item_import_config_manually_trojan">Type manually[Trojan]</string> <string name="menu_item_import_config_manually_trojan">Type manually[Trojan]</string>
<string name="menu_item_import_config_manually_wireguard">Type manually[Wireguard]</string> <string name="menu_item_import_config_manually_wireguard">Type manually[Wireguard]</string>
<string name="menu_item_import_config_manually_hysteria2">Type manually[Hysteria2]</string>
<string name="menu_item_import_config_custom">Custom config</string> <string name="menu_item_import_config_custom">Custom config</string>
<string name="menu_item_import_config_custom_clipboard">Import custom config from Clipboard</string> <string name="menu_item_import_config_custom_clipboard">Import custom config from Clipboard</string>
<string name="menu_item_import_config_custom_local">Import custom config from locally</string> <string name="menu_item_import_config_custom_local">Import custom config from locally</string>
@@ -110,7 +111,7 @@
<string name="msg_file_not_found">File not found</string> <string name="msg_file_not_found">File not found</string>
<string name="msg_remark_is_duplicate">The remarks already exists</string> <string name="msg_remark_is_duplicate">The remarks already exists</string>
<string name="toast_action_not_allowed">Action not allowed</string> <string name="toast_action_not_allowed">Action not allowed</string>
<string name="server_obfs_password">Obfs password</string>
<!-- PerAppProxyActivity --> <!-- PerAppProxyActivity -->
<string name="msg_dialog_progress">Loading</string> <string name="msg_dialog_progress">Loading</string>
@@ -130,6 +131,8 @@
<string name="title_vpn_settings">VPN Settings</string> <string name="title_vpn_settings">VPN Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string> <string name="title_pref_per_app_proxy">Per-app proxy</string>
<string name="summary_pref_per_app_proxy">General: Checked App is proxy, unchecked direct connection; \nbypass mode: checked app directly connected, unchecked proxy. \nThe option to automatically select the proxy application in the menu</string> <string name="summary_pref_per_app_proxy">General: Checked App is proxy, unchecked direct connection; \nbypass mode: checked app directly connected, unchecked proxy. \nThe option to automatically select the proxy application in the menu</string>
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_mux_settings">Mux Settings</string> <string name="title_mux_settings">Mux Settings</string>
<string name="title_pref_mux_enabled">Enable Mux</string> <string name="title_pref_mux_enabled">Enable Mux</string>
@@ -233,6 +236,7 @@
<string name="title_sub_setting">Subscription group setting</string> <string name="title_sub_setting">Subscription group setting</string>
<string name="sub_setting_remarks">remarks</string> <string name="sub_setting_remarks">remarks</string>
<string name="sub_setting_url">Optional URL</string> <string name="sub_setting_url">Optional URL</string>
<string name="sub_setting_filter">Remarks regular filter</string>
<string name="sub_setting_enable">Enable update</string> <string name="sub_setting_enable">Enable update</string>
<string name="sub_auto_update">Enable automatic update</string> <string name="sub_auto_update">Enable automatic update</string>
<string name="sub_setting_pre_profile">Previous proxy remarks</string> <string name="sub_setting_pre_profile">Previous proxy remarks</string>
@@ -252,13 +256,16 @@
<string name="routing_settings_domain_strategy">Domain strategy</string> <string name="routing_settings_domain_strategy">Domain strategy</string>
<string name="routing_settings_title">Routing Settings</string> <string name="routing_settings_title">Routing Settings</string>
<string name="routing_settings_tips">Separated by commas(,)</string> <string name="routing_settings_tips">Separated by commas(,),choose domain or ip</string>
<string name="routing_settings_save">Save</string> <string name="routing_settings_save">Save</string>
<string name="routing_settings_delete">Clear</string> <string name="routing_settings_delete">Clear</string>
<string name="routing_settings_rule_title">Routing Rule Settings</string> <string name="routing_settings_rule_title">Routing Rule Settings</string>
<string name="routing_settings_add_rule">Add rule</string> <string name="routing_settings_add_rule">Add rule</string>
<string name="routing_settings_import_rulesets">Import ruleset</string> <string name="routing_settings_import_rulesets">Import ruleset</string>
<string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string> <string name="routing_settings_import_rulesets_tip">Existing rulesets will be deleted, are you sure to continue?</string>
<string name="routing_settings_import_rulesets_from_clipboard">Import ruleset from clipboard</string>
<string name="routing_settings_export_rulesets_to_clipboard">Export ruleset to clipboard</string>
<string name="routing_settings_locked">Locked, keep this rule when import presets</string>
<string name="routing_settings_domain" translatable="false">domain</string> <string name="routing_settings_domain" translatable="false">domain</string>
<string name="routing_settings_ip" translatable="false">ip</string> <string name="routing_settings_ip" translatable="false">ip</string>
<string name="routing_settings_port" translatable="false">port</string> <string name="routing_settings_port" translatable="false">port</string>
@@ -311,6 +318,7 @@
<item>China Whitelist</item> <item>China Whitelist</item>
<item>China Blacklist</item> <item>China Blacklist</item>
<item>Global</item> <item>Global</item>
<item>Iran Whitelist</item>
</string-array> </string-array>
</resources> </resources>
@@ -13,6 +13,10 @@
android:key="pref_route_only_enabled" android:key="pref_route_only_enabled"
android:summary="@string/summary_pref_route_only_enabled" android:summary="@string/summary_pref_route_only_enabled"
android:title="@string/title_pref_route_only_enabled" /> android:title="@string/title_pref_route_only_enabled" />
<CheckBoxPreference
android:key="pref_is_booted"
android:summary="@string/summary_pref_is_booted"
android:title="@string/title_pref_is_booted" />
<PreferenceCategory android:title="@string/title_vpn_settings"> <PreferenceCategory android:title="@string/title_vpn_settings">
<CheckBoxPreference <CheckBoxPreference
+3 -3
View File
@@ -1,18 +1,18 @@
[versions] [versions]
activityKtx = "1.9.2" activityKtx = "1.9.3"
appcompat = "1.7.0" appcompat = "1.7.0"
cardview = "1.0.0" cardview = "1.0.0"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
core = "3.5.3" core = "3.5.3"
editorkit = "2.9.0" editorkit = "2.9.0"
flexbox = "3.0.0" flexbox = "3.0.0"
fragmentKtx = "1.8.3" fragmentKtx = "1.8.4"
gson = "2.11.0" gson = "2.11.0"
junit = "4.13.2" junit = "4.13.2"
kotlinReflect = "2.0.20" kotlinReflect = "2.0.20"
kotlinxCoroutinesCore = "1.9.0" kotlinxCoroutinesCore = "1.9.0"
legacySupportV4 = "1.0.0" legacySupportV4 = "1.0.0"
lifecycleViewmodelKtx = "2.8.5" lifecycleViewmodelKtx = "2.8.6"
material = "1.12.0" material = "1.12.0"
mmkvStatic = "1.3.9" mmkvStatic = "1.3.9"
multidex = "2.0.1" multidex = "2.0.1"