mirror of
https://github.com/2dust/v2rayNG.git
synced 2025-06-30 13:19:51 +00:00
Compare commits
82 commits
Author | SHA1 | Date | |
---|---|---|---|
|
3bf911da9c | ||
|
3f778a1ea2 | ||
|
8e03de8055 | ||
|
1f42d7fc07 | ||
|
0700e834f1 | ||
|
777190e861 | ||
|
33572477fc | ||
|
2fb6e62e13 | ||
|
94cc72d2b9 | ||
|
f68c353715 | ||
|
e077c18108 | ||
|
1a5e105212 | ||
|
e0881caab4 | ||
|
7219425258 | ||
|
51eabe5440 | ||
|
6f0b3ce990 | ||
|
69e27ed3bb | ||
|
fff6ab30e6 | ||
|
fdb67a86f4 | ||
|
ea088376ac | ||
|
52332d960e | ||
|
3ead542e2b | ||
|
9d1f98ff34 | ||
|
f305e26a39 | ||
|
aa47fba20d | ||
|
69c5bbfd3d | ||
|
90ed02804c | ||
|
822c1de79c | ||
|
d910b93525 | ||
|
7e6b1c247b | ||
|
f3f2b7fab5 | ||
|
e6f260da76 | ||
|
55bc2bf934 | ||
|
f22454da5d | ||
|
4a87549fa7 | ||
|
d447adc97f | ||
|
3773962b64 | ||
|
be0a2506ce | ||
|
7f9cb8dfdd | ||
|
71a5b6e480 | ||
|
02e53ced50 | ||
|
42c27a5e7e | ||
|
af04bbcf87 | ||
|
9bedfe8a7b | ||
|
2fdf684ee7 | ||
|
5b79951da7 | ||
|
06aa680d45 | ||
|
cdb9b1811c | ||
|
0fc1f2f5d3 | ||
|
ef1bb3dd34 | ||
|
1bca321d3f | ||
|
247e2b3ba3 | ||
|
41fd2b0cfb | ||
|
72da42ee40 | ||
|
c130d55e8f | ||
|
5ae84f7eac | ||
|
df5ea251e1 | ||
|
8890d9f004 | ||
|
4fcb3f9d06 | ||
|
5bf7c98cd3 | ||
|
46bc1a49df | ||
|
21175f41ec | ||
|
864c63987e | ||
|
4ac0547e22 | ||
|
12a9ee262c | ||
|
cfa9c19c94 | ||
|
56e33e6cdd | ||
|
02421072c1 | ||
|
b862a0dc65 | ||
|
1f25d6a000 | ||
|
e1def0616a | ||
|
83fd6efc17 | ||
|
f0c0e2e83a | ||
|
6ca3eb769e | ||
|
963d24ab66 | ||
|
cfd81441fa | ||
|
4084ae2938 | ||
|
3f9bc098ec | ||
|
9cb28ed969 | ||
|
773ddc5373 | ||
|
38193b5621 | ||
|
358713a2a3 |
64 changed files with 1558 additions and 908 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit 2715a3b110f64e039a4b8d2c8ca0cb5a9a6b0958
|
Subproject commit 8ad3e1ddf165d8d67e488346b2faa9153d3e33a4
|
|
@ -3,16 +3,12 @@
|
||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
[](https://developer.android.com/about/versions/lollipop)
|
[](https://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
[](https://t.me/v2rayn)
|
[](https://t.me/v2rayn)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
|
|
||||||
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
### Telegram Channel
|
### Telegram Channel
|
||||||
[github_2dust](https://t.me/github_2dust)
|
[github_2dust](https://t.me/github_2dust)
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ android {
|
||||||
applicationId = "com.v2ray.ang"
|
applicationId = "com.v2ray.ang"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 646
|
versionCode = 658
|
||||||
versionName = "1.9.46"
|
versionName = "1.10.8"
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||||
|
|
|
@ -144,6 +144,9 @@
|
||||||
<data android:host="install-sub" />
|
<data android:host="install-sub" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.CheckUpdateActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.AboutActivity"
|
android:name=".ui.AboutActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"routing": {
|
"routing": {
|
||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "AsIs",
|
||||||
"rules": []
|
"rules": []
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": {
|
||||||
|
|
|
@ -26,6 +26,7 @@ object AppConfig {
|
||||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||||
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
|
const val PREF_VPN_BYPASS_LAN = "pref_vpn_bypass_lan"
|
||||||
|
const val PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX = "pref_vpn_interface_address_config_index"
|
||||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
|
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
|
||||||
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
const val PREF_MUX_ENABLED = "pref_mux_enabled"
|
||||||
|
@ -55,6 +56,7 @@ object AppConfig {
|
||||||
const val PREF_DNS_HOSTS = "pref_dns_hosts"
|
const val PREF_DNS_HOSTS = "pref_dns_hosts"
|
||||||
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
|
||||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||||
|
const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method"
|
||||||
const val PREF_MODE = "pref_mode"
|
const val PREF_MODE = "pref_mode"
|
||||||
const val PREF_IS_BOOTED = "pref_is_booted"
|
const val PREF_IS_BOOTED = "pref_is_booted"
|
||||||
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
|
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
|
||||||
|
@ -103,6 +105,7 @@ object AppConfig {
|
||||||
const val TG_CHANNEL_URL = "https://t.me/github_2dust"
|
const val TG_CHANNEL_URL = "https://t.me/github_2dust"
|
||||||
const val DELAY_TEST_URL = "https://www.gstatic.com/generate_204"
|
const val DELAY_TEST_URL = "https://www.gstatic.com/generate_204"
|
||||||
const val DELAY_TEST_URL2 = "https://www.google.com/generate_204"
|
const val DELAY_TEST_URL2 = "https://www.google.com/generate_204"
|
||||||
|
const val IP_API_URL = "https://speed.cloudflare.com/meta"
|
||||||
|
|
||||||
/** DNS server addresses. */
|
/** DNS server addresses. */
|
||||||
const val DNS_PROXY = "1.1.1.1"
|
const val DNS_PROXY = "1.1.1.1"
|
||||||
|
@ -167,7 +170,9 @@ object AppConfig {
|
||||||
// Android Private DNS constants
|
// Android Private DNS constants
|
||||||
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
const val DNS_DNSPOD_DOMAIN = "dot.pub"
|
||||||
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
const val DNS_ALIDNS_DOMAIN = "dns.alidns.com"
|
||||||
const val DNS_CLOUDFLARE_DOMAIN = "one.one.one.one"
|
const val DNS_CLOUDFLARE_ONE_DOMAIN = "one.one.one.one"
|
||||||
|
const val DNS_CLOUDFLARE_DNS_COM_DOMAIN = "dns.cloudflare.com"
|
||||||
|
const val DNS_CLOUDFLARE_DNS_DOMAIN = "cloudflare-dns.com"
|
||||||
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
const val DNS_GOOGLE_DOMAIN = "dns.google"
|
||||||
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
const val DNS_QUAD9_DOMAIN = "dns.quad9.net"
|
||||||
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
const val DNS_YANDEX_DOMAIN = "common.dot.dns.yandex.net"
|
||||||
|
@ -181,14 +186,16 @@ object AppConfig {
|
||||||
const val HEADER_TYPE_HTTP = "http"
|
const val HEADER_TYPE_HTTP = "http"
|
||||||
|
|
||||||
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
|
||||||
val DNS_CLOUDFLARE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
val DNS_CLOUDFLARE_ONE_ADDRESSES = arrayListOf("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001")
|
||||||
|
val DNS_CLOUDFLARE_DNS_COM_ADDRESSES = arrayListOf("104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5")
|
||||||
|
val DNS_CLOUDFLARE_DNS_ADDRESSES = arrayListOf("104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9")
|
||||||
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
val DNS_DNSPOD_ADDRESSES = arrayListOf("1.12.12.12", "120.53.53.53")
|
||||||
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
val DNS_GOOGLE_ADDRESSES = arrayListOf("8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844")
|
||||||
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
val DNS_QUAD9_ADDRESSES = arrayListOf("9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9")
|
||||||
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
val DNS_YANDEX_ADDRESSES = arrayListOf("77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff")
|
||||||
|
|
||||||
//minimum list https://serverfault.com/a/304791
|
//minimum list https://serverfault.com/a/304791
|
||||||
val BYPASS_PRIVATE_IP_LIST = arrayListOf(
|
val ROUTED_IP_LIST = arrayListOf(
|
||||||
"0.0.0.0/5",
|
"0.0.0.0/5",
|
||||||
"8.0.0.0/7",
|
"8.0.0.0/7",
|
||||||
"11.0.0.0/8",
|
"11.0.0.0/8",
|
||||||
|
@ -223,7 +230,9 @@ object AppConfig {
|
||||||
)
|
)
|
||||||
|
|
||||||
val PRIVATE_IP_LIST = arrayListOf(
|
val PRIVATE_IP_LIST = arrayListOf(
|
||||||
|
"0.0.0.0/8",
|
||||||
"10.0.0.0/8",
|
"10.0.0.0/8",
|
||||||
|
"127.0.0.0/8",
|
||||||
"172.16.0.0/12",
|
"172.16.0.0/12",
|
||||||
"192.168.0.0/16",
|
"192.168.0.0/16",
|
||||||
"169.254.0.0/16",
|
"169.254.0.0/16",
|
||||||
|
|
|
@ -4,6 +4,6 @@ data class ConfigResult(
|
||||||
var status: Boolean,
|
var status: Boolean,
|
||||||
var guid: String? = null,
|
var guid: String? = null,
|
||||||
var content: String = "",
|
var content: String = "",
|
||||||
var domainPort: String? = null,
|
var socksPort: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
12
V2rayNG/app/src/main/java/com/v2ray/ang/dto/IPAPIInfo.kt
Normal file
12
V2rayNG/app/src/main/java/com/v2ray/ang/dto/IPAPIInfo.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class IPAPIInfo(
|
||||||
|
var ip: String? = null,
|
||||||
|
var clientIp: String? = null,
|
||||||
|
var ip_addr: String? = null,
|
||||||
|
var query: String? = null,
|
||||||
|
var country: String? = null,
|
||||||
|
var country_name: String? = null,
|
||||||
|
var country_code: String? = null,
|
||||||
|
var countryCode: String? = null
|
||||||
|
)
|
|
@ -1,9 +0,0 @@
|
||||||
package com.v2ray.ang.dto
|
|
||||||
|
|
||||||
data class ProfileLiteItem(
|
|
||||||
val configType: EConfigType,
|
|
||||||
var subscriptionId: String = "",
|
|
||||||
var remarks: String = "",
|
|
||||||
var server: String?,
|
|
||||||
var serverPort: Int?,
|
|
||||||
)
|
|
|
@ -11,5 +11,6 @@ data class SubscriptionItem(
|
||||||
var prevProfile: String? = null,
|
var prevProfile: String? = null,
|
||||||
var nextProfile: String? = null,
|
var nextProfile: String? = null,
|
||||||
var filter: String? = null,
|
var filter: String? = null,
|
||||||
|
var allowInsecureUrl: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.JsonPrimitive
|
|
||||||
import com.google.gson.JsonSerializationContext
|
|
||||||
import com.google.gson.JsonSerializer
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.ServersBean
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.VnextBean
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.lang.reflect.Type
|
|
||||||
|
|
||||||
data class V2rayConfig(
|
data class V2rayConfig(
|
||||||
var remarks: String? = null,
|
var remarks: String? = null,
|
||||||
var stats: Any? = null,
|
var stats: Any? = null,
|
||||||
val log: LogBean,
|
val log: LogBean,
|
||||||
var policy: PolicyBean?,
|
var policy: PolicyBean? = null,
|
||||||
val inbounds: ArrayList<InboundBean>,
|
val inbounds: ArrayList<InboundBean>,
|
||||||
var outbounds: ArrayList<OutboundBean>,
|
var outbounds: ArrayList<OutboundBean>,
|
||||||
var dns: DnsBean? = null,
|
var dns: DnsBean? = null,
|
||||||
|
@ -34,9 +23,9 @@ data class V2rayConfig(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class LogBean(
|
data class LogBean(
|
||||||
val access: String,
|
val access: String? = null,
|
||||||
val error: String,
|
val error: String? = null,
|
||||||
var loglevel: String?,
|
var loglevel: String? = null,
|
||||||
val dnsLog: Boolean? = null
|
val dnsLog: Boolean? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +35,7 @@ data class V2rayConfig(
|
||||||
var protocol: String,
|
var protocol: String,
|
||||||
var listen: String? = null,
|
var listen: String? = null,
|
||||||
val settings: Any? = null,
|
val settings: Any? = null,
|
||||||
val sniffing: SniffingBean?,
|
val sniffing: SniffingBean? = null,
|
||||||
val streamSettings: Any? = null,
|
val streamSettings: Any? = null,
|
||||||
val allocate: Any? = null
|
val allocate: Any? = null
|
||||||
) {
|
) {
|
||||||
|
@ -77,50 +66,6 @@ data class V2rayConfig(
|
||||||
val sendThrough: String? = null,
|
val sendThrough: String? = null,
|
||||||
var mux: MuxBean? = MuxBean(false)
|
var mux: MuxBean? = MuxBean(false)
|
||||||
) {
|
) {
|
||||||
companion object {
|
|
||||||
fun create(configType: EConfigType): OutboundBean? {
|
|
||||||
return when (configType) {
|
|
||||||
EConfigType.VMESS,
|
|
||||||
EConfigType.VLESS ->
|
|
||||||
return OutboundBean(
|
|
||||||
protocol = configType.name.lowercase(),
|
|
||||||
settings = OutSettingsBean(
|
|
||||||
vnext = listOf(
|
|
||||||
VnextBean(
|
|
||||||
users = listOf(UsersBean())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
streamSettings = StreamSettingsBean()
|
|
||||||
)
|
|
||||||
|
|
||||||
EConfigType.SHADOWSOCKS,
|
|
||||||
EConfigType.SOCKS,
|
|
||||||
EConfigType.HTTP,
|
|
||||||
EConfigType.TROJAN,
|
|
||||||
EConfigType.HYSTERIA2 ->
|
|
||||||
return OutboundBean(
|
|
||||||
protocol = configType.name.lowercase(),
|
|
||||||
settings = OutSettingsBean(
|
|
||||||
servers = listOf(ServersBean())
|
|
||||||
),
|
|
||||||
streamSettings = StreamSettingsBean()
|
|
||||||
)
|
|
||||||
|
|
||||||
EConfigType.WIREGUARD ->
|
|
||||||
return OutboundBean(
|
|
||||||
protocol = configType.name.lowercase(),
|
|
||||||
settings = OutSettingsBean(
|
|
||||||
secretKey = "",
|
|
||||||
peers = listOf(WireGuardBean())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
EConfigType.CUSTOM -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class OutSettingsBean(
|
data class OutSettingsBean(
|
||||||
var vnext: List<VnextBean>? = null,
|
var vnext: List<VnextBean>? = null,
|
||||||
var fragment: FragmentBean? = null,
|
var fragment: FragmentBean? = null,
|
||||||
|
@ -197,7 +142,7 @@ data class V2rayConfig(
|
||||||
|
|
||||||
data class WireGuardBean(
|
data class WireGuardBean(
|
||||||
var publicKey: String = "",
|
var publicKey: String = "",
|
||||||
var preSharedKey: String = "",
|
var preSharedKey: String? = null,
|
||||||
var endpoint: String = ""
|
var endpoint: String = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -299,7 +244,8 @@ data class V2rayConfig(
|
||||||
var tcpFastOpen: Boolean? = null,
|
var tcpFastOpen: Boolean? = null,
|
||||||
var tproxy: String? = null,
|
var tproxy: String? = null,
|
||||||
var mark: Int? = null,
|
var mark: Int? = null,
|
||||||
var dialerProxy: String? = null
|
var dialerProxy: String? = null,
|
||||||
|
var domainStrategy: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class TlsSettingsBean(
|
data class TlsSettingsBean(
|
||||||
|
@ -349,139 +295,6 @@ data class V2rayConfig(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun populateTransportSettings(
|
|
||||||
transport: String,
|
|
||||||
headerType: String?,
|
|
||||||
host: String?,
|
|
||||||
path: String?,
|
|
||||||
seed: String?,
|
|
||||||
quicSecurity: String?,
|
|
||||||
key: String?,
|
|
||||||
mode: String?,
|
|
||||||
serviceName: String?,
|
|
||||||
authority: String?
|
|
||||||
): String? {
|
|
||||||
var sni: String? = null
|
|
||||||
network = if (transport.isEmpty()) NetworkType.TCP.type else transport
|
|
||||||
when (network) {
|
|
||||||
NetworkType.TCP.type -> {
|
|
||||||
val tcpSetting = TcpSettingsBean()
|
|
||||||
if (headerType == AppConfig.HEADER_TYPE_HTTP) {
|
|
||||||
tcpSetting.header.type = AppConfig.HEADER_TYPE_HTTP
|
|
||||||
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
|
||||||
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
|
||||||
requestObj.headers.Host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
requestObj.path = path.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
tcpSetting.header.request = requestObj
|
|
||||||
sni = requestObj.headers.Host?.getOrNull(0)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tcpSetting.header.type = "none"
|
|
||||||
sni = host
|
|
||||||
}
|
|
||||||
tcpSettings = tcpSetting
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkType.KCP.type -> {
|
|
||||||
val kcpsetting = KcpSettingsBean()
|
|
||||||
kcpsetting.header.type = headerType ?: "none"
|
|
||||||
if (seed.isNullOrEmpty()) {
|
|
||||||
kcpsetting.seed = null
|
|
||||||
} else {
|
|
||||||
kcpsetting.seed = seed
|
|
||||||
}
|
|
||||||
if (host.isNullOrEmpty()) {
|
|
||||||
kcpsetting.header.domain = null
|
|
||||||
} else {
|
|
||||||
kcpsetting.header.domain = host
|
|
||||||
}
|
|
||||||
kcpSettings = kcpsetting
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkType.WS.type -> {
|
|
||||||
val wssetting = WsSettingsBean()
|
|
||||||
wssetting.headers.Host = host.orEmpty()
|
|
||||||
sni = host
|
|
||||||
wssetting.path = path ?: "/"
|
|
||||||
wsSettings = wssetting
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkType.HTTP_UPGRADE.type -> {
|
|
||||||
val httpupgradeSetting = HttpupgradeSettingsBean()
|
|
||||||
httpupgradeSetting.host = host.orEmpty()
|
|
||||||
sni = host
|
|
||||||
httpupgradeSetting.path = path ?: "/"
|
|
||||||
httpupgradeSettings = httpupgradeSetting
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkType.XHTTP.type -> {
|
|
||||||
val xhttpSetting = XhttpSettingsBean()
|
|
||||||
xhttpSetting.host = host.orEmpty()
|
|
||||||
sni = host
|
|
||||||
xhttpSetting.path = path ?: "/"
|
|
||||||
xhttpSettings = xhttpSetting
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkType.H2.type, NetworkType.HTTP.type -> {
|
|
||||||
network = NetworkType.H2.type
|
|
||||||
val h2Setting = HttpSettingsBean()
|
|
||||||
h2Setting.host = host.orEmpty().split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
sni = h2Setting.host.getOrNull(0)
|
|
||||||
h2Setting.path = path ?: "/"
|
|
||||||
httpSettings = h2Setting
|
|
||||||
}
|
|
||||||
|
|
||||||
// "quic" -> {
|
|
||||||
// val quicsetting = QuicSettingBean()
|
|
||||||
// quicsetting.security = quicSecurity ?: "none"
|
|
||||||
// quicsetting.key = key.orEmpty()
|
|
||||||
// quicsetting.header.type = headerType ?: "none"
|
|
||||||
// quicSettings = quicsetting
|
|
||||||
// }
|
|
||||||
|
|
||||||
NetworkType.GRPC.type -> {
|
|
||||||
val grpcSetting = GrpcSettingsBean()
|
|
||||||
grpcSetting.multiMode = mode == "multi"
|
|
||||||
grpcSetting.serviceName = serviceName.orEmpty()
|
|
||||||
grpcSetting.authority = authority.orEmpty()
|
|
||||||
grpcSetting.idle_timeout = 60
|
|
||||||
grpcSetting.health_check_timeout = 20
|
|
||||||
sni = authority
|
|
||||||
grpcSettings = grpcSetting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sni
|
|
||||||
}
|
|
||||||
|
|
||||||
fun populateTlsSettings(
|
|
||||||
streamSecurity: String,
|
|
||||||
allowInsecure: Boolean,
|
|
||||||
sni: String?,
|
|
||||||
fingerprint: String?,
|
|
||||||
alpns: String?,
|
|
||||||
publicKey: String?,
|
|
||||||
shortId: String?,
|
|
||||||
spiderX: String?
|
|
||||||
) {
|
|
||||||
security = if (streamSecurity.isEmpty()) null else streamSecurity
|
|
||||||
if (security == null) return
|
|
||||||
val tlsSetting = TlsSettingsBean(
|
|
||||||
allowInsecure = allowInsecure,
|
|
||||||
serverName = if (sni.isNullOrEmpty()) null else sni,
|
|
||||||
fingerprint = if (fingerprint.isNullOrEmpty()) null else fingerprint,
|
|
||||||
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
|
|
||||||
publicKey = if (publicKey.isNullOrEmpty()) null else publicKey,
|
|
||||||
shortId = if (shortId.isNullOrEmpty()) null else shortId,
|
|
||||||
spiderX = if (spiderX.isNullOrEmpty()) null else spiderX,
|
|
||||||
)
|
|
||||||
if (security == AppConfig.TLS) {
|
|
||||||
tlsSettings = tlsSetting
|
|
||||||
realitySettings = null
|
|
||||||
} else if (security == AppConfig.REALITY) {
|
|
||||||
tlsSettings = null
|
|
||||||
realitySettings = tlsSetting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MuxBean(
|
data class MuxBean(
|
||||||
|
@ -647,6 +460,18 @@ data class V2rayConfig(
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ensureSockopt(): V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean {
|
||||||
|
val stream = streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean().also {
|
||||||
|
streamSettings = it
|
||||||
|
}
|
||||||
|
|
||||||
|
val sockopt = stream.sockopt ?: V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean().also {
|
||||||
|
stream.sockopt = it
|
||||||
|
}
|
||||||
|
|
||||||
|
return sockopt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DnsBean(
|
data class DnsBean(
|
||||||
|
@ -723,15 +548,9 @@ data class V2rayConfig(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toPrettyPrinting(): String {
|
fun getAllProxyOutbound(): List<OutboundBean> {
|
||||||
return GsonBuilder()
|
return outbounds.filter { outbound ->
|
||||||
.setPrettyPrinting()
|
EConfigType.entries.any { it.name.equals(outbound.protocol, ignoreCase = true) }
|
||||||
.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()
|
|
||||||
.toJson(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VPN interface address configuration enum class
|
||||||
|
* Defines predefined IPv4 and IPv6 address pairs for VPN TUN interface configuration.
|
||||||
|
* Each option provides client and router addresses to establish point-to-point VPN tunnels.
|
||||||
|
*/
|
||||||
|
enum class VpnInterfaceAddressConfig(
|
||||||
|
val displayName: String,
|
||||||
|
val ipv4Client: String,
|
||||||
|
val ipv4Router: String,
|
||||||
|
val ipv6Client: String,
|
||||||
|
val ipv6Router: String
|
||||||
|
) {
|
||||||
|
OPTION_1("10.10.14.x", "10.10.14.1", "10.10.14.2", "fc00::10:10:14:1", "fc00::10:10:14:2"),
|
||||||
|
OPTION_2("10.1.0.x", "10.1.0.1", "10.1.0.2", "fc00::10:1:0:1", "fc00::10:1:0:2"),
|
||||||
|
OPTION_3("10.0.0.x", "10.0.0.1", "10.0.0.2", "fc00::10:0:0:1", "fc00::10:0:0:2"),
|
||||||
|
OPTION_4("172.31.0.x", "172.31.0.1", "172.31.0.2", "fc00::172:31:0:1", "fc00::172:31:0:2"),
|
||||||
|
OPTION_5("172.20.0.x", "172.20.0.1", "172.20.0.2", "fc00::172:20:0:1", "fc00::172:20:0:2"),
|
||||||
|
OPTION_6("172.16.0.x", "172.16.0.1", "172.16.0.2", "fc00::172:16:0:1", "fc00::172:16:0:2"),
|
||||||
|
OPTION_7("192.168.100.x", "192.168.100.1", "192.168.100.2", "fc00::192:168:100:1", "fc00::192:168:100:2");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Retrieves the VPN interface address configuration based on the specified index.
|
||||||
|
*
|
||||||
|
* @param index The configuration index (0-based) corresponding to user selection
|
||||||
|
* @return The VpnInterfaceAddressConfig instance at the specified index,
|
||||||
|
* or OPTION_1 (default) if the index is out of bounds
|
||||||
|
*/
|
||||||
|
fun getConfigByIndex(index: Int): VpnInterfaceAddressConfig {
|
||||||
|
return if (index in values().indices) {
|
||||||
|
values()[index]
|
||||||
|
} else {
|
||||||
|
OPTION_1 // Default to the first configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.NetworkType
|
import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.util.HttpUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ open class FmtBase {
|
||||||
val url = String.format(
|
val url = String.format(
|
||||||
"%s@%s:%s",
|
"%s@%s:%s",
|
||||||
Utils.urlEncode(userInfo ?: ""),
|
Utils.urlEncode(userInfo ?: ""),
|
||||||
Utils.getIpv6Address(config.server),
|
Utils.getIpv6Address(HttpUtil.toIdnDomain(config.server.orEmpty())),
|
||||||
config.serverPort
|
config.serverPort
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -148,4 +150,21 @@ open class FmtBase {
|
||||||
|
|
||||||
return dicQuery
|
return dicQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getServerAddress(profileItem: ProfileItem): String {
|
||||||
|
if (Utils.isPureIpAddress(profileItem.server.orEmpty())) {
|
||||||
|
return profileItem.server.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val domain = HttpUtil.toIdnDomain(profileItem.server.orEmpty())
|
||||||
|
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") != "2") {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
//Resolve and replace domain
|
||||||
|
val resolvedIps = HttpUtil.resolveHostToIP(domain, MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
|
||||||
|
if (resolvedIps.isNullOrEmpty()) {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
return resolvedIps.first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
|
|
||||||
object HttpFmt : FmtBase() {
|
object HttpFmt : FmtBase() {
|
||||||
/**
|
/**
|
||||||
|
@ -13,10 +14,10 @@ object HttpFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.HTTP)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HTTP)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = getServerAddress(profileItem)
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
if (profileItem.username.isNotNullEmpty()) {
|
if (profileItem.username.isNotNullEmpty()) {
|
||||||
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ object Hysteria2Fmt : FmtBase() {
|
||||||
val config = ProfileItem.create(EConfigType.HYSTERIA2)
|
val config = ProfileItem.create(EConfigType.HYSTERIA2)
|
||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
@ -144,7 +145,7 @@ object Hysteria2Fmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.HYSTERIA2)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.HYSTERIA2)
|
||||||
return outboundBean
|
return outboundBean
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import com.v2ray.ang.dto.NetworkType
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ object ShadowsocksFmt : FmtBase() {
|
||||||
if (uri.port <= 0) return null
|
if (uri.port <= 0) return null
|
||||||
if (uri.userInfo.isNullOrEmpty()) return null
|
if (uri.userInfo.isNullOrEmpty()) return null
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
|
@ -131,38 +132,22 @@ object ShadowsocksFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.SHADOWSOCKS)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SHADOWSOCKS)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = getServerAddress(profileItem)
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
server.password = profileItem.password
|
server.password = profileItem.password
|
||||||
server.method = profileItem.method
|
server.method = profileItem.method
|
||||||
}
|
}
|
||||||
|
|
||||||
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.let {
|
||||||
profileItem.network.orEmpty(),
|
V2rayConfigManager.populateTransportSettings(it, profileItem)
|
||||||
profileItem.headerType,
|
}
|
||||||
profileItem.host,
|
|
||||||
profileItem.path,
|
|
||||||
profileItem.seed,
|
|
||||||
profileItem.quicSecurity,
|
|
||||||
profileItem.quicKey,
|
|
||||||
profileItem.mode,
|
|
||||||
profileItem.serviceName,
|
|
||||||
profileItem.authority,
|
|
||||||
)
|
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.let {
|
||||||
profileItem.security.orEmpty(),
|
V2rayConfigManager.populateTlsSettings(it, profileItem, sni)
|
||||||
profileItem.insecure == true,
|
}
|
||||||
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
|
||||||
profileItem.fingerPrint,
|
|
||||||
profileItem.alpn,
|
|
||||||
profileItem.publicKey,
|
|
||||||
profileItem.shortId,
|
|
||||||
profileItem.spiderX,
|
|
||||||
)
|
|
||||||
|
|
||||||
return outboundBean
|
return outboundBean
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ object SocksFmt : FmtBase() {
|
||||||
if (uri.idnHost.isEmpty()) return null
|
if (uri.idnHost.isEmpty()) return null
|
||||||
if (uri.port <= 0) return null
|
if (uri.port <= 0) return null
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
|
@ -60,10 +61,10 @@ object SocksFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.SOCKS)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.SOCKS)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = getServerAddress(profileItem)
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
if (profileItem.username.isNotNullEmpty()) {
|
if (profileItem.username.isNotNullEmpty()) {
|
||||||
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ object TrojanFmt : FmtBase() {
|
||||||
val config = ProfileItem.create(EConfigType.TROJAN)
|
val config = ProfileItem.create(EConfigType.TROJAN)
|
||||||
|
|
||||||
val uri = URI(Utils.fixIllegalUrl(str))
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
@ -60,38 +61,22 @@ object TrojanFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.TROJAN)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.TROJAN)
|
||||||
|
|
||||||
outboundBean?.settings?.servers?.first()?.let { server ->
|
outboundBean?.settings?.servers?.first()?.let { server ->
|
||||||
server.address = profileItem.server.orEmpty()
|
server.address = getServerAddress(profileItem)
|
||||||
server.port = profileItem.serverPort.orEmpty().toInt()
|
server.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
server.password = profileItem.password
|
server.password = profileItem.password
|
||||||
server.flow = profileItem.flow
|
server.flow = profileItem.flow
|
||||||
}
|
}
|
||||||
|
|
||||||
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.let {
|
||||||
profileItem.network.orEmpty(),
|
V2rayConfigManager.populateTransportSettings(it, profileItem)
|
||||||
profileItem.headerType,
|
}
|
||||||
profileItem.host,
|
|
||||||
profileItem.path,
|
|
||||||
profileItem.seed,
|
|
||||||
profileItem.quicSecurity,
|
|
||||||
profileItem.quicKey,
|
|
||||||
profileItem.mode,
|
|
||||||
profileItem.serviceName,
|
|
||||||
profileItem.authority,
|
|
||||||
)
|
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.let {
|
||||||
profileItem.security.orEmpty(),
|
V2rayConfigManager.populateTlsSettings(it, profileItem, sni)
|
||||||
profileItem.insecure == true,
|
}
|
||||||
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
|
||||||
profileItem.fingerPrint,
|
|
||||||
profileItem.alpn,
|
|
||||||
profileItem.publicKey,
|
|
||||||
profileItem.shortId,
|
|
||||||
profileItem.spiderX,
|
|
||||||
)
|
|
||||||
|
|
||||||
return outboundBean
|
return outboundBean
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ object VlessFmt : FmtBase() {
|
||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
@ -57,41 +57,23 @@ object VlessFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.VLESS)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VLESS)
|
||||||
|
|
||||||
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||||
vnext.address = profileItem.server.orEmpty()
|
vnext.address = getServerAddress(profileItem)
|
||||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
vnext.users[0].id = profileItem.password.orEmpty()
|
vnext.users[0].id = profileItem.password.orEmpty()
|
||||||
vnext.users[0].encryption = profileItem.method
|
vnext.users[0].encryption = profileItem.method
|
||||||
vnext.users[0].flow = profileItem.flow
|
vnext.users[0].flow = profileItem.flow
|
||||||
}
|
}
|
||||||
|
|
||||||
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.let {
|
||||||
profileItem.network.orEmpty(),
|
V2rayConfigManager.populateTransportSettings(it, profileItem)
|
||||||
profileItem.headerType,
|
}
|
||||||
profileItem.host,
|
|
||||||
profileItem.path,
|
|
||||||
profileItem.seed,
|
|
||||||
profileItem.quicSecurity,
|
|
||||||
profileItem.quicKey,
|
|
||||||
profileItem.mode,
|
|
||||||
profileItem.serviceName,
|
|
||||||
profileItem.authority,
|
|
||||||
)
|
|
||||||
outboundBean?.streamSettings?.xhttpSettings?.mode = profileItem.xhttpMode
|
|
||||||
outboundBean?.streamSettings?.xhttpSettings?.extra = JsonUtil.parseString(profileItem.xhttpExtra)
|
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.let {
|
||||||
profileItem.security.orEmpty(),
|
V2rayConfigManager.populateTlsSettings(it, profileItem, sni)
|
||||||
profileItem.insecure == true,
|
}
|
||||||
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
|
||||||
profileItem.fingerPrint,
|
|
||||||
profileItem.alpn,
|
|
||||||
profileItem.publicKey,
|
|
||||||
profileItem.shortId,
|
|
||||||
profileItem.spiderX,
|
|
||||||
)
|
|
||||||
|
|
||||||
return outboundBean
|
return outboundBean
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.v2ray.ang.dto.VmessQRCode
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.extension.isNotNullEmpty
|
import com.v2ray.ang.extension.isNotNullEmpty
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
@ -150,7 +151,7 @@ object VmessFmt : FmtBase() {
|
||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
config.password = uri.userInfo
|
config.password = uri.userInfo
|
||||||
|
@ -168,38 +169,22 @@ object VmessFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.VMESS)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.VMESS)
|
||||||
|
|
||||||
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
outboundBean?.settings?.vnext?.first()?.let { vnext ->
|
||||||
vnext.address = profileItem.server.orEmpty()
|
vnext.address = getServerAddress(profileItem)
|
||||||
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
vnext.port = profileItem.serverPort.orEmpty().toInt()
|
||||||
vnext.users[0].id = profileItem.password.orEmpty()
|
vnext.users[0].id = profileItem.password.orEmpty()
|
||||||
vnext.users[0].security = profileItem.method
|
vnext.users[0].security = profileItem.method
|
||||||
}
|
}
|
||||||
|
|
||||||
val sni = outboundBean?.streamSettings?.populateTransportSettings(
|
val sni = outboundBean?.streamSettings?.let {
|
||||||
profileItem.network.orEmpty(),
|
V2rayConfigManager.populateTransportSettings(it, profileItem)
|
||||||
profileItem.headerType,
|
}
|
||||||
profileItem.host,
|
|
||||||
profileItem.path,
|
|
||||||
profileItem.seed,
|
|
||||||
profileItem.quicSecurity,
|
|
||||||
profileItem.quicKey,
|
|
||||||
profileItem.mode,
|
|
||||||
profileItem.serviceName,
|
|
||||||
profileItem.authority,
|
|
||||||
)
|
|
||||||
|
|
||||||
outboundBean?.streamSettings?.populateTlsSettings(
|
outboundBean?.streamSettings?.let {
|
||||||
profileItem.security.orEmpty(),
|
V2rayConfigManager.populateTlsSettings(it, profileItem, sni)
|
||||||
profileItem.insecure == true,
|
}
|
||||||
if (profileItem.sni.isNullOrEmpty()) sni else profileItem.sni,
|
|
||||||
profileItem.fingerPrint,
|
|
||||||
profileItem.alpn,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
return outboundBean
|
return outboundBean
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||||
import com.v2ray.ang.extension.idnHost
|
import com.v2ray.ang.extension.idnHost
|
||||||
import com.v2ray.ang.extension.removeWhiteSpace
|
import com.v2ray.ang.extension.removeWhiteSpace
|
||||||
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
@ -24,14 +25,14 @@ object WireguardFmt : FmtBase() {
|
||||||
if (uri.rawQuery.isNullOrEmpty()) return null
|
if (uri.rawQuery.isNullOrEmpty()) return null
|
||||||
val queryParam = getQueryParam(uri)
|
val queryParam = getQueryParam(uri)
|
||||||
|
|
||||||
config.remarks = Utils.urlDecode(uri.fragment.orEmpty())
|
config.remarks = Utils.urlDecode(uri.fragment.orEmpty()).let { if (it.isEmpty()) "none" else it }
|
||||||
config.server = uri.idnHost
|
config.server = uri.idnHost
|
||||||
config.serverPort = uri.port.toString()
|
config.serverPort = uri.port.toString()
|
||||||
|
|
||||||
config.secretKey = uri.userInfo.orEmpty()
|
config.secretKey = uri.userInfo.orEmpty()
|
||||||
config.localAddress = queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
|
config.localAddress = queryParam["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
config.publicKey = queryParam["publickey"].orEmpty()
|
config.publicKey = queryParam["publickey"].orEmpty()
|
||||||
config.preSharedKey = queryParam["presharedkey"].orEmpty()
|
config.preSharedKey = queryParam["presharedkey"]?.takeIf { it.isNotEmpty() }
|
||||||
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
config.mtu = Utils.parseInt(queryParam["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
config.reserved = queryParam["reserved"] ?: "0,0,0"
|
config.reserved = queryParam["reserved"] ?: "0,0,0"
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ object WireguardFmt : FmtBase() {
|
||||||
config.localAddress = interfaceParams["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
|
config.localAddress = interfaceParams["address"] ?: WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
config.mtu = Utils.parseInt(interfaceParams["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
config.mtu = Utils.parseInt(interfaceParams["mtu"] ?: AppConfig.WIREGUARD_LOCAL_MTU)
|
||||||
config.publicKey = peerParams["publickey"].orEmpty()
|
config.publicKey = peerParams["publickey"].orEmpty()
|
||||||
config.preSharedKey = peerParams["presharedkey"].orEmpty()
|
config.preSharedKey = peerParams["presharedkey"]?.takeIf { it.isNotEmpty() }
|
||||||
val endpoint = peerParams["endpoint"].orEmpty()
|
val endpoint = peerParams["endpoint"].orEmpty()
|
||||||
val endpointParts = endpoint.split(":", limit = 2)
|
val endpointParts = endpoint.split(":", limit = 2)
|
||||||
if (endpointParts.size == 2) {
|
if (endpointParts.size == 2) {
|
||||||
|
@ -105,18 +106,18 @@ object WireguardFmt : FmtBase() {
|
||||||
* @return the converted OutboundBean object, or null if conversion fails
|
* @return the converted OutboundBean object, or null if conversion fails
|
||||||
*/
|
*/
|
||||||
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
fun toOutbound(profileItem: ProfileItem): OutboundBean? {
|
||||||
val outboundBean = OutboundBean.create(EConfigType.WIREGUARD)
|
val outboundBean = V2rayConfigManager.createInitOutbound(EConfigType.WIREGUARD)
|
||||||
|
|
||||||
outboundBean?.settings?.let { wireguard ->
|
outboundBean?.settings?.let { wireguard ->
|
||||||
wireguard.secretKey = profileItem.secretKey
|
wireguard.secretKey = profileItem.secretKey
|
||||||
wireguard.address = (profileItem.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4).split(",")
|
wireguard.address = (profileItem.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4).split(",")
|
||||||
wireguard.peers?.firstOrNull()?.let { peer ->
|
wireguard.peers?.firstOrNull()?.let { peer ->
|
||||||
peer.publicKey = profileItem.publicKey.orEmpty()
|
peer.publicKey = profileItem.publicKey.orEmpty()
|
||||||
peer.preSharedKey = profileItem.preSharedKey.orEmpty()
|
peer.preSharedKey = profileItem.preSharedKey?.takeIf { it.isNotEmpty() }
|
||||||
peer.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
|
peer.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
|
||||||
}
|
}
|
||||||
wireguard.mtu = profileItem.mtu
|
wireguard.mtu = profileItem.mtu
|
||||||
wireguard.reserved = profileItem.reserved?.split(",")?.map { it.toInt() }
|
wireguard.reserved = profileItem.reserved?.takeIf { it.isNotBlank() }?.split(",")?.filter { it.isNotBlank() }?.map { it.trim().toInt() }
|
||||||
}
|
}
|
||||||
|
|
||||||
return outboundBean
|
return outboundBean
|
||||||
|
|
|
@ -415,12 +415,14 @@ object AngConfigManager {
|
||||||
if (!it.second.enabled) {
|
if (!it.second.enabled) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val url = HttpUtil.idnToASCII(it.second.url)
|
val url = HttpUtil.toIdnUrl(it.second.url)
|
||||||
if (!Utils.isValidUrl(url)) {
|
if (!Utils.isValidUrl(url)) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if (!Utils.isValidSubUrl(url)) {
|
if (!it.second.allowInsecureUrl) {
|
||||||
return 0
|
if (!Utils.isValidSubUrl(url)) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.i(AppConfig.TAG, url)
|
Log.i(AppConfig.TAG, url)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.RoutingType
|
import com.v2ray.ang.dto.RoutingType
|
||||||
import com.v2ray.ang.dto.RulesetItem
|
import com.v2ray.ang.dto.RulesetItem
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.dto.VpnInterfaceAddressConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
import com.v2ray.ang.handler.MmkvManager.decodeServerConfig
|
||||||
import com.v2ray.ang.handler.MmkvManager.decodeServerList
|
import com.v2ray.ang.handler.MmkvManager.decodeServerList
|
||||||
import com.v2ray.ang.util.JsonUtil
|
import com.v2ray.ang.util.JsonUtil
|
||||||
|
@ -159,7 +160,7 @@ object SettingsManager {
|
||||||
* @return True if bypassing LAN, false otherwise.
|
* @return True if bypassing LAN, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun routingRulesetsBypassLan(): Boolean {
|
fun routingRulesetsBypassLan(): Boolean {
|
||||||
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "0"
|
val vpnBypassLan = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_BYPASS_LAN) ?: "1"
|
||||||
if (vpnBypassLan == "1") {
|
if (vpnBypassLan == "1") {
|
||||||
return true
|
return true
|
||||||
} else if (vpnBypassLan == "2") {
|
} else if (vpnBypassLan == "2") {
|
||||||
|
@ -216,7 +217,7 @@ object SettingsManager {
|
||||||
* @return The ProfileItem.
|
* @return The ProfileItem.
|
||||||
*/
|
*/
|
||||||
fun getServerViaRemarks(remarks: String?): ProfileItem? {
|
fun getServerViaRemarks(remarks: String?): ProfileItem? {
|
||||||
if (remarks == null) {
|
if (remarks.isNullOrEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val serverList = decodeServerList()
|
val serverList = decodeServerList()
|
||||||
|
@ -356,4 +357,17 @@ object SettingsManager {
|
||||||
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the currently selected VPN interface address configuration.
|
||||||
|
* This method reads the user's preference for VPN interface addressing and returns
|
||||||
|
* the corresponding configuration containing IPv4 and IPv6 addresses.
|
||||||
|
*
|
||||||
|
* @return The selected VpnInterfaceAddressConfig instance, or the default configuration
|
||||||
|
* if no valid selection is found or if the stored index is invalid.
|
||||||
|
*/
|
||||||
|
fun getCurrentVpnInterfaceAddressConfig(): VpnInterfaceAddressConfig {
|
||||||
|
val selectedIndex = MmkvManager.decodeSettingsString(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX, "0")?.toInt()
|
||||||
|
return VpnInterfaceAddressConfig.getConfigByIndex(selectedIndex ?: 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.dto.IPAPIInfo
|
||||||
import com.v2ray.ang.extension.responseLength
|
import com.v2ray.ang.extension.responseLength
|
||||||
import com.v2ray.ang.util.HttpUtil
|
import com.v2ray.ang.util.HttpUtil
|
||||||
|
import com.v2ray.ang.util.JsonUtil
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -164,6 +166,17 @@ object SpeedtestManager {
|
||||||
return Pair(elapsed, result)
|
return Pair(elapsed, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getRemoteIPInfo(): String? {
|
||||||
|
val httpPort = SettingsManager.getHttpPort()
|
||||||
|
var content = HttpUtil.getUrlContent(AppConfig.IP_API_URL, 5000, httpPort) ?: return null
|
||||||
|
|
||||||
|
var ipInfo = JsonUtil.fromJson(content, IPAPIInfo::class.java) ?: return null
|
||||||
|
var ip = ipInfo.ip ?: ipInfo.clientIp ?: ipInfo.ip_addr ?: ipInfo.query
|
||||||
|
var country = ipInfo.country_code ?: ipInfo.country ?: ipInfo.countryCode
|
||||||
|
|
||||||
|
return "(${country ?: "unknown"}) $ip"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the version of the V2Ray library.
|
* Gets the version of the V2Ray library.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,7 +17,6 @@ import java.io.FileOutputStream
|
||||||
|
|
||||||
object UpdateCheckerManager {
|
object UpdateCheckerManager {
|
||||||
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
|
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
|
||||||
try {
|
|
||||||
val url = if (includePreRelease) {
|
val url = if (includePreRelease) {
|
||||||
AppConfig.APP_API_URL
|
AppConfig.APP_API_URL
|
||||||
} else {
|
} else {
|
||||||
|
@ -53,10 +52,6 @@ object UpdateCheckerManager {
|
||||||
} else {
|
} else {
|
||||||
CheckUpdateResult(hasUpdate = false)
|
CheckUpdateResult(hasUpdate = false)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
|
|
||||||
return@withContext CheckUpdateResult(hasUpdate = false, error = e.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
|
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -158,6 +158,7 @@ object NotificationService {
|
||||||
mBuilder = null
|
mBuilder = null
|
||||||
speedNotificationJob?.cancel()
|
speedNotificationJob?.cancel()
|
||||||
speedNotificationJob = null
|
speedNotificationJob = null
|
||||||
|
mNotificationManager = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,14 +25,13 @@ class QSTileService : TileService() {
|
||||||
* @param state The state to set.
|
* @param state The state to set.
|
||||||
*/
|
*/
|
||||||
fun setState(state: Int) {
|
fun setState(state: Int) {
|
||||||
|
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||||
if (state == Tile.STATE_INACTIVE) {
|
if (state == Tile.STATE_INACTIVE) {
|
||||||
qsTile?.state = Tile.STATE_INACTIVE
|
qsTile?.state = Tile.STATE_INACTIVE
|
||||||
qsTile?.label = getString(R.string.app_name)
|
qsTile?.label = getString(R.string.app_name)
|
||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
|
||||||
} else if (state == Tile.STATE_ACTIVE) {
|
} else if (state == Tile.STATE_ACTIVE) {
|
||||||
qsTile?.state = Tile.STATE_ACTIVE
|
qsTile?.state = Tile.STATE_ACTIVE
|
||||||
qsTile?.label = V2RayServiceManager.getRunningServerName()
|
qsTile?.label = V2RayServiceManager.getRunningServerName()
|
||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qsTile?.updateTile()
|
qsTile?.updateTile()
|
||||||
|
@ -45,7 +44,11 @@ class QSTileService : TileService() {
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
|
||||||
setState(Tile.STATE_INACTIVE)
|
if (V2RayServiceManager.isRunning()) {
|
||||||
|
setState(Tile.STATE_ACTIVE)
|
||||||
|
} else {
|
||||||
|
setState(Tile.STATE_INACTIVE)
|
||||||
|
}
|
||||||
mMsgReceive = ReceiveMessageHandler(this)
|
mMsgReceive = ReceiveMessageHandler(this)
|
||||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)
|
||||||
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
|
ContextCompat.registerReceiver(applicationContext, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||||
|
|
|
@ -27,7 +27,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||||
* @return The start mode.
|
* @return The start mode.
|
||||||
*/
|
*/
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
V2RayServiceManager.startV2rayPoint()
|
V2RayServiceManager.startCoreLoop()
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||||
*/
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
V2RayServiceManager.stopV2rayPoint()
|
V2RayServiceManager.stopCoreLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.handler.SettingsManager
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
|
import com.v2ray.ang.handler.SpeedtestManager
|
||||||
import com.v2ray.ang.handler.V2rayConfigManager
|
import com.v2ray.ang.handler.V2rayConfigManager
|
||||||
import com.v2ray.ang.util.MessageUtil
|
import com.v2ray.ang.util.MessageUtil
|
||||||
import com.v2ray.ang.util.PluginUtil
|
import com.v2ray.ang.util.PluginUtil
|
||||||
|
@ -23,14 +24,14 @@ import go.Seq
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import libv2ray.CoreCallbackHandler
|
||||||
|
import libv2ray.CoreController
|
||||||
import libv2ray.Libv2ray
|
import libv2ray.Libv2ray
|
||||||
import libv2ray.V2RayPoint
|
|
||||||
import libv2ray.V2RayVPNServiceSupportsSet
|
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
|
|
||||||
object V2RayServiceManager {
|
object V2RayServiceManager {
|
||||||
|
|
||||||
private val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
private val coreController: CoreController = Libv2ray.newCoreController(CoreCallback())
|
||||||
private val mMsgReceive = ReceiveMessageHandler()
|
private val mMsgReceive = ReceiveMessageHandler()
|
||||||
private var currentConfig: ProfileItem? = null
|
private var currentConfig: ProfileItem? = null
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ object V2RayServiceManager {
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
Seq.setContext(value?.get()?.getService()?.applicationContext)
|
Seq.setContext(value?.get()?.getService()?.applicationContext)
|
||||||
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
|
Libv2ray.initCoreEnv(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,7 +81,7 @@ object V2RayServiceManager {
|
||||||
* Checks if the V2Ray service is running.
|
* Checks if the V2Ray service is running.
|
||||||
* @return True if the service is running, false otherwise.
|
* @return True if the service is running, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isRunning() = v2rayPoint.isRunning
|
fun isRunning() = coreController.isRunning
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the name of the currently running server.
|
* Gets the name of the currently running server.
|
||||||
|
@ -90,15 +91,18 @@ object V2RayServiceManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the context service for V2Ray.
|
* Starts the context service for V2Ray.
|
||||||
|
* Chooses between VPN service or Proxy-only service based on user settings.
|
||||||
* @param context The context from which the service is started.
|
* @param context The context from which the service is started.
|
||||||
*/
|
*/
|
||||||
private fun startContextService(context: Context) {
|
private fun startContextService(context: Context) {
|
||||||
if (v2rayPoint.isRunning) return
|
if (coreController.isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val guid = MmkvManager.getSelectServer() ?: return
|
val guid = MmkvManager.getSelectServer() ?: return
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
||||||
if (config.configType != EConfigType.CUSTOM
|
if (config.configType != EConfigType.CUSTOM
|
||||||
&& !Utils.isValidUrl(config.server)
|
&& !Utils.isValidUrl(config.server)
|
||||||
&& !Utils.isIpAddress(config.server)
|
&& !Utils.isPureIpAddress(config.server.orEmpty())
|
||||||
) return
|
) return
|
||||||
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
// val result = V2rayConfigUtil.getV2rayConfig(context, guid)
|
||||||
// if (!result.status) return
|
// if (!result.status) return
|
||||||
|
@ -123,18 +127,19 @@ object V2RayServiceManager {
|
||||||
/**
|
/**
|
||||||
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||||
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
|
* `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
|
||||||
* Starts the V2Ray point.
|
* Starts the V2Ray core service.
|
||||||
*/
|
*/
|
||||||
fun startV2rayPoint() {
|
fun startCoreLoop(): Boolean {
|
||||||
val service = getService() ?: return
|
if (coreController.isRunning) {
|
||||||
val guid = MmkvManager.getSelectServer() ?: return
|
return false
|
||||||
val config = MmkvManager.decodeServerConfig(guid) ?: return
|
|
||||||
if (v2rayPoint.isRunning) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val service = getService() ?: return false
|
||||||
|
val guid = MmkvManager.getSelectServer() ?: return false
|
||||||
|
val config = MmkvManager.decodeServerConfig(guid) ?: return false
|
||||||
val result = V2rayConfigManager.getV2rayConfig(service, guid)
|
val result = V2rayConfigManager.getV2rayConfig(service, guid)
|
||||||
if (!result.status)
|
if (!result.status)
|
||||||
return
|
return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
|
@ -144,39 +149,49 @@ object V2RayServiceManager {
|
||||||
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
|
ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to register broadcast receiver", e)
|
Log.e(AppConfig.TAG, "Failed to register broadcast receiver", e)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
v2rayPoint.configureFileContent = result.content
|
|
||||||
v2rayPoint.domainName = result.domainPort
|
|
||||||
currentConfig = config
|
currentConfig = config
|
||||||
|
|
||||||
try {
|
try {
|
||||||
v2rayPoint.runLoop(MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6))
|
coreController.startLoop(result.content)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to start V2Ray loop", e)
|
Log.e(AppConfig.TAG, "Failed to start Core loop", e)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v2rayPoint.isRunning) {
|
if (coreController.isRunning == false) {
|
||||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
|
||||||
NotificationService.showNotification(currentConfig)
|
|
||||||
|
|
||||||
PluginUtil.runPlugin(service, config, result.domainPort)
|
|
||||||
} else {
|
|
||||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||||
NotificationService.cancelNotification()
|
NotificationService.cancelNotification()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||||
|
NotificationService.showNotification(currentConfig)
|
||||||
|
NotificationService.startSpeedNotification(currentConfig)
|
||||||
|
|
||||||
|
PluginUtil.runPlugin(service, config, result.socksPort)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to startup service", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the V2Ray point.
|
* Stops the V2Ray core service.
|
||||||
|
* Unregisters broadcast receivers, stops notifications, and shuts down plugins.
|
||||||
|
* @return True if the core was stopped successfully, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun stopV2rayPoint() {
|
fun stopCoreLoop(): Boolean {
|
||||||
val service = getService() ?: return
|
val service = getService() ?: return false
|
||||||
|
|
||||||
if (v2rayPoint.isRunning) {
|
if (coreController.isRunning) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
v2rayPoint.stopLoop()
|
coreController.stopLoop()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to stop V2Ray loop", e)
|
Log.e(AppConfig.TAG, "Failed to stop V2Ray loop", e)
|
||||||
}
|
}
|
||||||
|
@ -192,6 +207,8 @@ object V2RayServiceManager {
|
||||||
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
|
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
|
||||||
}
|
}
|
||||||
PluginUtil.stopPlugin()
|
PluginUtil.stopPlugin()
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,40 +218,52 @@ object V2RayServiceManager {
|
||||||
* @return The statistics value.
|
* @return The statistics value.
|
||||||
*/
|
*/
|
||||||
fun queryStats(tag: String, link: String): Long {
|
fun queryStats(tag: String, link: String): Long {
|
||||||
return v2rayPoint.queryStats(tag, link)
|
return coreController.queryStats(tag, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measures the delay for V2Ray.
|
* Measures the connection delay for the current V2Ray configuration.
|
||||||
|
* Tests with primary URL first, then falls back to alternative URL if needed.
|
||||||
|
* Also fetches remote IP information if the delay test was successful.
|
||||||
*/
|
*/
|
||||||
private fun measureV2rayDelay() {
|
private fun measureV2rayDelay() {
|
||||||
|
if (coreController.isRunning == false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val service = getService() ?: return@launch
|
val service = getService() ?: return@launch
|
||||||
var time = -1L
|
var time = -1L
|
||||||
var errstr = ""
|
var errorStr = ""
|
||||||
if (v2rayPoint.isRunning) {
|
|
||||||
try {
|
try {
|
||||||
time = v2rayPoint.measureDelay(SettingsManager.getDelayTestUrl())
|
time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
|
Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
|
||||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
errorStr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
}
|
|
||||||
if (time == -1L) {
|
|
||||||
try {
|
|
||||||
time = v2rayPoint.measureDelay(SettingsManager.getDelayTestUrl(true))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
|
|
||||||
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val result = if (time == -1L) {
|
if (time == -1L) {
|
||||||
service.getString(R.string.connection_test_error, errstr)
|
try {
|
||||||
} else {
|
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
|
||||||
service.getString(R.string.connection_test_available, time)
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
|
||||||
|
errorStr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val result = if (time >= 0) {
|
||||||
|
service.getString(R.string.connection_test_available, time)
|
||||||
|
} else {
|
||||||
|
service.getString(R.string.connection_test_error, errorStr)
|
||||||
|
}
|
||||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
|
||||||
|
|
||||||
|
// Only fetch IP info if the delay test was successful
|
||||||
|
if (time >= 0) {
|
||||||
|
SpeedtestManager.getRemoteIPInfo()?.let { ip ->
|
||||||
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, "$result\n$ip")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,10 +275,25 @@ object V2RayServiceManager {
|
||||||
return serviceControl?.get()?.getService()
|
return serviceControl?.get()?.getService()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class V2RayCallback : V2RayVPNServiceSupportsSet {
|
/**
|
||||||
|
* Core callback handler implementation for handling V2Ray core events.
|
||||||
|
* Handles startup, shutdown, socket protection, and status emission.
|
||||||
|
*/
|
||||||
|
private class CoreCallback : CoreCallbackHandler {
|
||||||
|
/**
|
||||||
|
* Called when V2Ray core starts up.
|
||||||
|
* @return 0 for success, any other value for failure.
|
||||||
|
*/
|
||||||
|
override fun startup(): Long {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when V2Ray core shuts down.
|
||||||
|
* @return 0 for success, any other value for failure.
|
||||||
|
*/
|
||||||
override fun shutdown(): Long {
|
override fun shutdown(): Long {
|
||||||
val serviceControl = serviceControl?.get() ?: return -1
|
val serviceControl = serviceControl?.get() ?: return -1
|
||||||
// called by go
|
|
||||||
return try {
|
return try {
|
||||||
serviceControl.stopService()
|
serviceControl.stopService()
|
||||||
0
|
0
|
||||||
|
@ -259,46 +303,25 @@ object V2RayServiceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepare(): Long {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun protect(l: Long): Boolean {
|
|
||||||
val serviceControl = serviceControl?.get() ?: return true
|
|
||||||
return serviceControl.vpnProtect(l.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by Go to emit status.
|
* Called when V2Ray core emits status information.
|
||||||
* @param l The status code.
|
* @param l Status code.
|
||||||
* @param s The status message.
|
* @param s Status message.
|
||||||
* @return The status code.
|
* @return Always returns 0.
|
||||||
*/
|
*/
|
||||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by Go to set up the service.
|
|
||||||
* @param s The setup string.
|
|
||||||
* @return The status code.
|
|
||||||
*/
|
|
||||||
override fun setup(s: String): Long {
|
|
||||||
val serviceControl = serviceControl?.get() ?: return -1
|
|
||||||
return try {
|
|
||||||
serviceControl.startService()
|
|
||||||
NotificationService.startSpeedNotification(currentConfig)
|
|
||||||
0
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(AppConfig.TAG, "Failed to setup service in callback", e)
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast receiver for handling messages sent to the service.
|
||||||
|
* Handles registration, service control, and screen events.
|
||||||
|
*/
|
||||||
private class ReceiveMessageHandler : BroadcastReceiver() {
|
private class ReceiveMessageHandler : BroadcastReceiver() {
|
||||||
/**
|
/**
|
||||||
* Handles received broadcast messages.
|
* Handles received broadcast messages.
|
||||||
|
* Processes service control messages and screen state changes.
|
||||||
* @param ctx The context in which the receiver is running.
|
* @param ctx The context in which the receiver is running.
|
||||||
* @param intent The intent being received.
|
* @param intent The intent being received.
|
||||||
*/
|
*/
|
||||||
|
@ -306,7 +329,7 @@ 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 -> {
|
||||||
if (v2rayPoint.isRunning) {
|
if (coreController.isRunning) {
|
||||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
|
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
|
||||||
} else {
|
} else {
|
||||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||||
|
|
|
@ -32,7 +32,7 @@ class V2RayTestService : Service() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Seq.setContext(this)
|
Seq.setContext(this)
|
||||||
Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
|
Libv2ray.initCoreEnv(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,12 +33,7 @@ import java.lang.ref.SoftReference
|
||||||
class V2RayVpnService : VpnService(), ServiceControl {
|
class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
companion object {
|
companion object {
|
||||||
private const val VPN_MTU = 1500
|
private const val VPN_MTU = 1500
|
||||||
private const val PRIVATE_VLAN4_CLIENT = "10.10.14.1"
|
|
||||||
private const val PRIVATE_VLAN4_ROUTER = "10.10.14.2"
|
|
||||||
private const val PRIVATE_VLAN6_CLIENT = "fc00::10:10:14:1"
|
|
||||||
private const val PRIVATE_VLAN6_ROUTER = "fc00::10:10:14:2"
|
|
||||||
private const val TUN2SOCKS = "libtun2socks.so"
|
private const val TUN2SOCKS = "libtun2socks.so"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mInterface: ParcelFileDescriptor
|
private lateinit var mInterface: ParcelFileDescriptor
|
||||||
|
@ -104,7 +99,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
V2RayServiceManager.startV2rayPoint()
|
if (V2RayServiceManager.startCoreLoop()) {
|
||||||
|
startService()
|
||||||
|
}
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
//return super.onStartCommand(intent, flags, startId)
|
//return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
@ -158,14 +155,15 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
// If the old interface has exactly the same parameters, use it!
|
// If the old interface has exactly the same parameters, use it!
|
||||||
// Configure a builder while parsing the parameters.
|
// Configure a builder while parsing the parameters.
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
|
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
|
||||||
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
||||||
|
|
||||||
builder.setMtu(VPN_MTU)
|
builder.setMtu(VPN_MTU)
|
||||||
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
|
builder.addAddress(vpnConfig.ipv4Client, 30)
|
||||||
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||||
val bypassLan = SettingsManager.routingRulesetsBypassLan()
|
val bypassLan = SettingsManager.routingRulesetsBypassLan()
|
||||||
if (bypassLan) {
|
if (bypassLan) {
|
||||||
AppConfig.BYPASS_PRIVATE_IP_LIST.forEach {
|
AppConfig.ROUTED_IP_LIST.forEach {
|
||||||
val addr = it.split('/')
|
val addr = it.split('/')
|
||||||
builder.addRoute(addr[0], addr[1].toInt())
|
builder.addRoute(addr[0], addr[1].toInt())
|
||||||
}
|
}
|
||||||
|
@ -174,9 +172,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||||
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
builder.addAddress(vpnConfig.ipv6Client, 126)
|
||||||
if (bypassLan) {
|
if (bypassLan) {
|
||||||
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
|
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
|
||||||
|
builder.addRoute("fc00::", 18) //Xray-core default FakeIPv6 Pool
|
||||||
} else {
|
} else {
|
||||||
builder.addRoute("::", 0)
|
builder.addRoute("::", 0)
|
||||||
}
|
}
|
||||||
|
@ -255,10 +254,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
* Starts the tun2socks process with the appropriate parameters.
|
* Starts the tun2socks process with the appropriate parameters.
|
||||||
*/
|
*/
|
||||||
private fun runTun2socks() {
|
private fun runTun2socks() {
|
||||||
|
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
|
||||||
val socksPort = SettingsManager.getSocksPort()
|
val socksPort = SettingsManager.getSocksPort()
|
||||||
|
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
|
||||||
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", vpnConfig.ipv4Router,
|
||||||
"--netif-netmask", "255.255.255.252",
|
"--netif-netmask", "255.255.255.252",
|
||||||
"--socks-server-addr", "$LOOPBACK:${socksPort}",
|
"--socks-server-addr", "$LOOPBACK:${socksPort}",
|
||||||
"--tunmtu", VPN_MTU.toString(),
|
"--tunmtu", VPN_MTU.toString(),
|
||||||
|
@ -269,7 +270,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
|
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
|
||||||
cmd.add("--netif-ip6addr")
|
cmd.add("--netif-ip6addr")
|
||||||
cmd.add(PRIVATE_VLAN6_ROUTER)
|
cmd.add(vpnConfig.ipv6Router)
|
||||||
}
|
}
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
|
||||||
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
||||||
|
@ -293,11 +294,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
runTun2socks()
|
runTun2socks()
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
Log.i(AppConfig.TAG, process.toString())
|
Log.i(AppConfig.TAG, "$TUN2SOCKS process info : ${process.toString()}")
|
||||||
|
|
||||||
sendFd()
|
sendFd()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to start tun2socks process", e)
|
Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,13 +309,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
private fun sendFd() {
|
private fun sendFd() {
|
||||||
val fd = mInterface.fileDescriptor
|
val fd = mInterface.fileDescriptor
|
||||||
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
||||||
Log.i(AppConfig.TAG, path)
|
Log.i(AppConfig.TAG, "LocalSocket path : $path")
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
var tries = 0
|
var tries = 0
|
||||||
while (true) try {
|
while (true) try {
|
||||||
Thread.sleep(50L shl tries)
|
Thread.sleep(50L shl tries)
|
||||||
Log.i(AppConfig.TAG, "sendFd tries: $tries")
|
Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries")
|
||||||
LocalSocket().use { localSocket ->
|
LocalSocket().use { localSocket ->
|
||||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||||
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
||||||
|
@ -348,13 +349,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log.i(AppConfig.TAG, "tun2socks destroy")
|
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy")
|
||||||
process.destroy()
|
process.destroy()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(AppConfig.TAG, "Failed to destroy tun2socks process", e)
|
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
V2RayServiceManager.stopV2rayPoint()
|
V2RayServiceManager.stopCoreLoop()
|
||||||
|
|
||||||
if (isForced) {
|
if (isForced) {
|
||||||
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
|
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
|
||||||
|
|
|
@ -5,28 +5,21 @@ import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityAboutBinding
|
import com.v2ray.ang.databinding.ActivityAboutBinding
|
||||||
import com.v2ray.ang.dto.CheckUpdateResult
|
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.toastError
|
import com.v2ray.ang.extension.toastError
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.handler.SpeedtestManager
|
import com.v2ray.ang.handler.SpeedtestManager
|
||||||
import com.v2ray.ang.handler.UpdateCheckerManager
|
|
||||||
import com.v2ray.ang.util.AppManagerUtil
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.util.ZipUtil
|
import com.v2ray.ang.util.ZipUtil
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -105,23 +98,6 @@ class AboutActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//If it is the Google Play version, not be displayed within 1 days after update
|
|
||||||
if (Utils.isGoogleFlavor()) {
|
|
||||||
val lastUpdateTime = AppManagerUtil.getLastUpdateTime(this)
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if ((currentTime - lastUpdateTime) < 1 * 24 * 60 * 60 * 1000L) {
|
|
||||||
binding.layoutCheckUpdate.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.layoutCheckUpdate.setOnClickListener {
|
|
||||||
checkForUpdates(binding.checkPreRelease.isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
|
|
||||||
}
|
|
||||||
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
|
|
||||||
|
|
||||||
binding.layoutSoureCcode.setOnClickListener {
|
binding.layoutSoureCcode.setOnClickListener {
|
||||||
Utils.openUri(this, AppConfig.APP_URL)
|
Utils.openUri(this, AppConfig.APP_URL)
|
||||||
}
|
}
|
||||||
|
@ -222,28 +198,4 @@ class AboutActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkForUpdates(includePreRelease: Boolean) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
|
|
||||||
if (result.hasUpdate) {
|
|
||||||
showUpdateDialog(result)
|
|
||||||
} else {
|
|
||||||
toast(R.string.update_already_latest_version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showUpdateDialog(result: CheckUpdateResult) {
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
|
|
||||||
.setMessage(result.releaseNotes)
|
|
||||||
.setPositiveButton(R.string.update_now) { _, _ ->
|
|
||||||
result.downloadUrl?.let {
|
|
||||||
Utils.openUri(this, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.BuildConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.databinding.ActivityCheckUpdateBinding
|
||||||
|
import com.v2ray.ang.dto.CheckUpdateResult
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.extension.toastError
|
||||||
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.SpeedtestManager
|
||||||
|
import com.v2ray.ang.handler.UpdateCheckerManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CheckUpdateActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private val binding by lazy { ActivityCheckUpdateBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
title = getString(R.string.update_check_for_update)
|
||||||
|
|
||||||
|
binding.layoutCheckUpdate.setOnClickListener {
|
||||||
|
checkForUpdates(binding.checkPreRelease.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.checkPreRelease.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
MmkvManager.encodeSettings(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, isChecked)
|
||||||
|
}
|
||||||
|
binding.checkPreRelease.isChecked = MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
|
||||||
|
|
||||||
|
"v${BuildConfig.VERSION_NAME} (${SpeedtestManager.getLibVersion()})".also {
|
||||||
|
binding.tvVersion.text = it
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForUpdates(binding.checkPreRelease.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkForUpdates(includePreRelease: Boolean) {
|
||||||
|
toast(R.string.update_checking_for_update)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val result = UpdateCheckerManager.checkForUpdate(includePreRelease)
|
||||||
|
if (result.hasUpdate) {
|
||||||
|
showUpdateDialog(result)
|
||||||
|
} else {
|
||||||
|
toastSuccess(R.string.update_already_latest_version)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to check for updates: ${e.message}")
|
||||||
|
toastError(e.message ?: getString(R.string.toast_failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUpdateDialog(result: CheckUpdateResult) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
|
||||||
|
.setMessage(result.releaseNotes)
|
||||||
|
.setPositiveButton(R.string.update_now) { _, _ ->
|
||||||
|
result.downloadUrl?.let {
|
||||||
|
Utils.openUri(this, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -685,6 +685,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||||
|
|
||||||
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}")
|
R.id.promotion -> Utils.openUri(this, "${Utils.decode(AppConfig.APP_PROMOTION_URL)}?t=${System.currentTimeMillis()}")
|
||||||
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
|
R.id.logcat -> startActivity(Intent(this, LogcatActivity::class.java))
|
||||||
|
R.id.check_for_update -> startActivity(Intent(this, CheckUpdateActivity::class.java))
|
||||||
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
|
R.id.about -> startActivity(Intent(this, AboutActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
import com.v2ray.ang.service.V2RayServiceManager
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -220,10 +221,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||||
* @param guid The server unique identifier
|
* @param guid The server unique identifier
|
||||||
*/
|
*/
|
||||||
private fun shareFullContent(guid: String) {
|
private fun shareFullContent(guid: String) {
|
||||||
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
|
mActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
mActivity.toastSuccess(R.string.toast_success)
|
val result = AngConfigManager.shareFullContent2Clipboard(mActivity, guid)
|
||||||
} else {
|
launch(Dispatchers.Main) {
|
||||||
mActivity.toastError(R.string.toast_failure)
|
if (result == 0) {
|
||||||
|
mActivity.toastSuccess(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
mActivity.toastError(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||||
import com.v2ray.ang.AppConfig.REALITY
|
import com.v2ray.ang.AppConfig.REALITY
|
||||||
import com.v2ray.ang.AppConfig.TLS
|
import com.v2ray.ang.AppConfig.TLS
|
||||||
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_MTU
|
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
@ -327,7 +326,7 @@ class ServerActivity : BaseActivity() {
|
||||||
et_preshared_key?.text = Utils.getEditable(config.preSharedKey.orEmpty())
|
et_preshared_key?.text = Utils.getEditable(config.preSharedKey.orEmpty())
|
||||||
et_reserved1?.text = Utils.getEditable(config.reserved ?: "0,0,0")
|
et_reserved1?.text = Utils.getEditable(config.reserved ?: "0,0,0")
|
||||||
et_local_address?.text = Utils.getEditable(
|
et_local_address?.text = Utils.getEditable(
|
||||||
config.localAddress ?: "$WIREGUARD_LOCAL_ADDRESS_V4,$WIREGUARD_LOCAL_ADDRESS_V6"
|
config.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4
|
||||||
)
|
)
|
||||||
et_local_mtu?.text = Utils.getEditable(config.mtu?.toString() ?: WIREGUARD_LOCAL_MTU)
|
et_local_mtu?.text = Utils.getEditable(config.mtu?.toString() ?: WIREGUARD_LOCAL_MTU)
|
||||||
} else if (config.configType == EConfigType.HYSTERIA2) {
|
} else if (config.configType == EConfigType.HYSTERIA2) {
|
||||||
|
@ -420,7 +419,7 @@ class ServerActivity : BaseActivity() {
|
||||||
et_public_key?.text = null
|
et_public_key?.text = null
|
||||||
et_reserved1?.text = Utils.getEditable("0,0,0")
|
et_reserved1?.text = Utils.getEditable("0,0,0")
|
||||||
et_local_address?.text =
|
et_local_address?.text =
|
||||||
Utils.getEditable("${WIREGUARD_LOCAL_ADDRESS_V4},${WIREGUARD_LOCAL_ADDRESS_V6}")
|
Utils.getEditable(WIREGUARD_LOCAL_ADDRESS_V4)
|
||||||
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
et_local_mtu?.text = Utils.getEditable(WIREGUARD_LOCAL_MTU)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ class SettingsActivity : BaseActivity() {
|
||||||
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||||
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
||||||
private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
|
private val vpnBypassLan by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_BYPASS_LAN) }
|
||||||
|
private val vpnInterfaceAddress by lazy { findPreference<ListPreference>(AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX) }
|
||||||
|
|
||||||
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
private val mux by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_MUX_ENABLED) }
|
||||||
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
private val muxConcurrency by lazy { findPreference<EditTextPreference>(AppConfig.PREF_MUX_CONCURRENCY) }
|
||||||
|
@ -249,12 +250,14 @@ class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
AppConfig.PREF_VPN_BYPASS_LAN,
|
AppConfig.PREF_VPN_BYPASS_LAN,
|
||||||
|
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
|
||||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||||
AppConfig.PREF_MUX_XUDP_QUIC,
|
AppConfig.PREF_MUX_XUDP_QUIC,
|
||||||
AppConfig.PREF_FRAGMENT_PACKETS,
|
AppConfig.PREF_FRAGMENT_PACKETS,
|
||||||
AppConfig.PREF_LANGUAGE,
|
AppConfig.PREF_LANGUAGE,
|
||||||
AppConfig.PREF_UI_MODE_NIGHT,
|
AppConfig.PREF_UI_MODE_NIGHT,
|
||||||
AppConfig.PREF_LOGLEVEL,
|
AppConfig.PREF_LOGLEVEL,
|
||||||
|
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
|
||||||
AppConfig.PREF_MODE
|
AppConfig.PREF_MODE
|
||||||
).forEach { key ->
|
).forEach { key ->
|
||||||
if (MmkvManager.decodeSettingsString(key) != null) {
|
if (MmkvManager.decodeSettingsString(key) != null) {
|
||||||
|
@ -273,7 +276,7 @@ class SettingsActivity : BaseActivity() {
|
||||||
localDnsPort?.isEnabled = vpn
|
localDnsPort?.isEnabled = vpn
|
||||||
vpnDns?.isEnabled = vpn
|
vpnDns?.isEnabled = vpn
|
||||||
vpnBypassLan?.isEnabled = vpn
|
vpnBypassLan?.isEnabled = vpn
|
||||||
vpn
|
vpnInterfaceAddress?.isEnabled = vpn
|
||||||
if (vpn) {
|
if (vpn) {
|
||||||
updateLocalDns(
|
updateLocalDns(
|
||||||
MmkvManager.decodeSettingsBool(
|
MmkvManager.decodeSettingsBool(
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
@ -46,6 +47,7 @@ class SubEditActivity : BaseActivity() {
|
||||||
binding.etFilter.text = Utils.getEditable(subItem.filter)
|
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.allowInsecureUrl.isChecked = subItem.allowInsecureUrl
|
||||||
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
|
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
|
||||||
binding.etNextProfile.text = Utils.getEditable(subItem.nextProfile)
|
binding.etNextProfile.text = Utils.getEditable(subItem.nextProfile)
|
||||||
return true
|
return true
|
||||||
|
@ -77,6 +79,7 @@ class SubEditActivity : BaseActivity() {
|
||||||
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
|
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
|
||||||
subItem.prevProfile = binding.etPreProfile.text.toString()
|
subItem.prevProfile = binding.etPreProfile.text.toString()
|
||||||
subItem.nextProfile = binding.etNextProfile.text.toString()
|
subItem.nextProfile = binding.etNextProfile.text.toString()
|
||||||
|
subItem.allowInsecureUrl = binding.allowInsecureUrl.isChecked
|
||||||
|
|
||||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||||
toast(R.string.sub_setting_remarks)
|
toast(R.string.sub_setting_remarks)
|
||||||
|
@ -90,7 +93,9 @@ class SubEditActivity : BaseActivity() {
|
||||||
|
|
||||||
if (!Utils.isValidSubUrl(subItem.url)) {
|
if (!Utils.isValidSubUrl(subItem.url)) {
|
||||||
toast(R.string.toast_insecure_url_protocol)
|
toast(R.string.toast_insecure_url_protocol)
|
||||||
return false
|
if (!subItem.allowInsecureUrl) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,19 +110,28 @@ class SubEditActivity : BaseActivity() {
|
||||||
*/
|
*/
|
||||||
private fun deleteServer(): Boolean {
|
private fun deleteServer(): Boolean {
|
||||||
if (editSubId.isNotEmpty()) {
|
if (editSubId.isNotEmpty()) {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
MmkvManager.removeSubscription(editSubId)
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
launch(Dispatchers.Main) {
|
MmkvManager.removeSubscription(editSubId)
|
||||||
finish()
|
launch(Dispatchers.Main) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
MmkvManager.removeSubscription(editSubId)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
}
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
@ -20,6 +21,8 @@ import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
import com.v2ray.ang.util.QRCodeDecoder
|
import com.v2ray.ang.util.QRCodeDecoder
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
|
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>(), ItemTouchHelperAdapter {
|
||||||
|
|
||||||
|
@ -46,6 +49,10 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.itemSubSettingBinding.layoutRemove.setOnClickListener {
|
||||||
|
removeSubscription(subId, position)
|
||||||
|
}
|
||||||
|
|
||||||
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked ->
|
||||||
if (!it.isPressed) return@setOnCheckedChangeListener
|
if (!it.isPressed) return@setOnCheckedChangeListener
|
||||||
subItem.enabled = isChecked
|
subItem.enabled = isChecked
|
||||||
|
@ -54,9 +61,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(subItem.url)) {
|
if (TextUtils.isEmpty(subItem.url)) {
|
||||||
|
holder.itemSubSettingBinding.layoutUrl.visibility = View.GONE
|
||||||
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
|
holder.itemSubSettingBinding.layoutShare.visibility = View.INVISIBLE
|
||||||
holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE
|
holder.itemSubSettingBinding.chkEnable.visibility = View.INVISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
holder.itemSubSettingBinding.layoutUrl.visibility = View.VISIBLE
|
||||||
holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE
|
holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE
|
||||||
holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE
|
holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE
|
||||||
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
|
holder.itemSubSettingBinding.layoutShare.setOnClickListener {
|
||||||
|
@ -90,6 +99,32 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeSubscription(subId: String, position: Int) {
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||||
|
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
removeSubscriptionSub(subId, position)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
//do noting
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
removeSubscriptionSub(subId, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeSubscriptionSub(subId: String, position: Int) {
|
||||||
|
mActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
MmkvManager.removeSubscription(subId)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemRangeChanged(position, mActivity.subscriptions.size)
|
||||||
|
mActivity.refreshData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||||
return MainViewHolder(
|
return MainViewHolder(
|
||||||
ItemRecyclerSubSettingBinding.inflate(
|
ItemRecyclerSubSettingBinding.inflate(
|
||||||
|
|
|
@ -9,20 +9,23 @@ import com.v2ray.ang.util.Utils.urlDecode
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.IDN
|
import java.net.IDN
|
||||||
|
import java.net.Inet6Address
|
||||||
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
object HttpUtil {
|
object HttpUtil {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a URL string to its ASCII representation.
|
* Converts the domain part of a URL string to its IDN (Punycode, ASCII Compatible Encoding) format.
|
||||||
*
|
*
|
||||||
* @param str The URL string to convert.
|
* For example, a URL like "https://例子.中国/path" will be converted to "https://xn--fsqu00a.xn--fiqs8s/path".
|
||||||
* @return The ASCII representation of the URL.
|
*
|
||||||
|
* @param str The URL string to convert (can contain non-ASCII characters in the domain).
|
||||||
|
* @return The URL string with the domain part converted to ASCII-compatible (Punycode) format.
|
||||||
*/
|
*/
|
||||||
fun idnToASCII(str: String): String {
|
fun toIdnUrl(str: String): String {
|
||||||
val url = URL(str)
|
val url = URL(str)
|
||||||
val host = url.host
|
val host = url.host
|
||||||
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
|
val asciiHost = IDN.toASCII(url.host, IDN.ALLOW_UNASSIGNED)
|
||||||
|
@ -33,6 +36,67 @@ object HttpUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Unicode domain name to its IDN (Punycode, ASCII Compatible Encoding) format.
|
||||||
|
* If the input is an IP address or already an ASCII domain, returns the original string.
|
||||||
|
*
|
||||||
|
* @param domain The domain string to convert (can include non-ASCII internationalized characters).
|
||||||
|
* @return The domain in ASCII-compatible (Punycode) format, or the original string if input is an IP or already ASCII.
|
||||||
|
*/
|
||||||
|
fun toIdnDomain(domain: String): String {
|
||||||
|
// Return as is if it's a pure IP address (IPv4 or IPv6)
|
||||||
|
if (Utils.isPureIpAddress(domain)) {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as is if already ASCII (English domain or already punycode)
|
||||||
|
if (domain.all { it.code < 128 }) {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, convert to ASCII using IDN
|
||||||
|
return IDN.toASCII(domain, IDN.ALLOW_UNASSIGNED)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a hostname to an IP address, returns original input if it's already an IP
|
||||||
|
*
|
||||||
|
* @param host The hostname or IP address to resolve
|
||||||
|
* @param ipv6Preferred Whether to prefer IPv6 addresses, defaults to false
|
||||||
|
* @return The resolved IP address or the original input (if it's already an IP or resolution fails)
|
||||||
|
*/
|
||||||
|
fun resolveHostToIP(host: String, ipv6Preferred: Boolean = false): List<String>? {
|
||||||
|
try {
|
||||||
|
// If it's already an IP address, return it as a list
|
||||||
|
if (Utils.isPureIpAddress(host)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all IP addresses
|
||||||
|
val addresses = InetAddress.getAllByName(host)
|
||||||
|
if (addresses.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort addresses based on preference
|
||||||
|
val sortedAddresses = if (ipv6Preferred) {
|
||||||
|
addresses.sortedWith(compareByDescending { it is Inet6Address })
|
||||||
|
} else {
|
||||||
|
addresses.sortedWith(compareBy { it is Inet6Address })
|
||||||
|
}
|
||||||
|
|
||||||
|
val ipList = sortedAddresses.mapNotNull { it.hostAddress }
|
||||||
|
|
||||||
|
Log.i(AppConfig.TAG, "Resolved IPs for $host: ${ipList.joinToString()}")
|
||||||
|
|
||||||
|
return ipList
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.TAG, "Failed to resolve host to IP", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the content of a URL as a string.
|
* Retrieves the content of a URL as a string.
|
||||||
*
|
*
|
||||||
|
|
|
@ -23,20 +23,24 @@ object PluginUtil {
|
||||||
*
|
*
|
||||||
* @param context The context to use.
|
* @param context The context to use.
|
||||||
* @param config The profile configuration.
|
* @param config The profile configuration.
|
||||||
* @param domainPort The domain and port information.
|
* @param socksPort The port information.
|
||||||
*/
|
*/
|
||||||
fun runPlugin(context: Context, config: ProfileItem?, domainPort: String?) {
|
fun runPlugin(context: Context, config: ProfileItem?, socksPort: Int?) {
|
||||||
Log.i(AppConfig.TAG, "Starting plugin execution")
|
Log.i(AppConfig.TAG, "Starting plugin execution")
|
||||||
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
|
Log.w(AppConfig.TAG, "Cannot run plugin: config is null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (config.configType == EConfigType.HYSTERIA2) {
|
if (config.configType == EConfigType.HYSTERIA2) {
|
||||||
|
if (socksPort == null) {
|
||||||
|
Log.w(AppConfig.TAG, "Cannot run plugin: socksPort is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
|
Log.i(AppConfig.TAG, "Running Hysteria2 plugin")
|
||||||
val configFile = genConfigHy2(context, config, domainPort) ?: return
|
val configFile = genConfigHy2(context, config, socksPort) ?: return
|
||||||
val cmd = genCmdHy2(context, configFile)
|
val cmd = genCmdHy2(context, configFile)
|
||||||
|
|
||||||
procService.runProcess(context, cmd)
|
procService.runProcess(context, cmd)
|
||||||
|
@ -66,7 +70,7 @@ object PluginUtil {
|
||||||
|
|
||||||
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
|
if (config?.configType?.equals(EConfigType.HYSTERIA2) == true) {
|
||||||
val socksPort = Utils.findFreePort(listOf(0))
|
val socksPort = Utils.findFreePort(listOf(0))
|
||||||
val configFile = genConfigHy2(context, config, "0:${socksPort}") ?: return retFailure
|
val configFile = genConfigHy2(context, config, socksPort) ?: return retFailure
|
||||||
val cmd = genCmdHy2(context, configFile)
|
val cmd = genCmdHy2(context, configFile)
|
||||||
|
|
||||||
val proc = ProcessService()
|
val proc = ProcessService()
|
||||||
|
@ -85,14 +89,12 @@ object PluginUtil {
|
||||||
*
|
*
|
||||||
* @param context The context to use.
|
* @param context The context to use.
|
||||||
* @param config The profile configuration.
|
* @param config The profile configuration.
|
||||||
* @param domainPort The domain and port information.
|
* @param socksPort The port information.
|
||||||
* @return The generated configuration file.
|
* @return The generated configuration file.
|
||||||
*/
|
*/
|
||||||
private fun genConfigHy2(context: Context, config: ProfileItem, domainPort: String?): File? {
|
private fun genConfigHy2(context: Context, config: ProfileItem, socksPort: Int): File? {
|
||||||
Log.i(AppConfig.TAG, "runPlugin $HYSTERIA2")
|
Log.i(AppConfig.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 hy2Config = Hysteria2Fmt.toNativeConfig(config, socksPort) ?: return null
|
||||||
|
|
||||||
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
|
val configFile = File(context.noBackupFilesDir, "hy2_${SystemClock.elapsedRealtime()}.json")
|
||||||
|
|
|
@ -198,6 +198,21 @@ object Utils {
|
||||||
return isIpv4Address(value) || isIpv6Address(value)
|
return isIpv4Address(value) || isIpv6Address(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid domain name.
|
||||||
|
*
|
||||||
|
* A valid domain name must not be an IP address and must be a valid URL format.
|
||||||
|
*
|
||||||
|
* @param input The string to check.
|
||||||
|
* @return True if the string is a valid domain name, false otherwise.
|
||||||
|
*/
|
||||||
|
fun isDomainName(input: String?): Boolean {
|
||||||
|
if (input.isNullOrEmpty()) return false
|
||||||
|
|
||||||
|
// Must not be an IP address and must be a valid URL format
|
||||||
|
return !isPureIpAddress(input) && isValidUrl(input)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a string is a valid IPv4 address.
|
* Check if a string is a valid IPv4 address.
|
||||||
*
|
*
|
||||||
|
|
|
@ -41,6 +41,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||||
AppConfig.PREF_MODE,
|
AppConfig.PREF_MODE,
|
||||||
AppConfig.PREF_VPN_DNS,
|
AppConfig.PREF_VPN_DNS,
|
||||||
AppConfig.PREF_VPN_BYPASS_LAN,
|
AppConfig.PREF_VPN_BYPASS_LAN,
|
||||||
|
AppConfig.PREF_VPN_INTERFACE_ADDRESS_CONFIG_INDEX,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
AppConfig.PREF_DNS_HOSTS,
|
AppConfig.PREF_DNS_HOSTS,
|
||||||
|
@ -48,6 +49,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||||
AppConfig.PREF_LOCAL_DNS_PORT,
|
AppConfig.PREF_LOCAL_DNS_PORT,
|
||||||
AppConfig.PREF_SOCKS_PORT,
|
AppConfig.PREF_SOCKS_PORT,
|
||||||
AppConfig.PREF_LOGLEVEL,
|
AppConfig.PREF_LOGLEVEL,
|
||||||
|
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
|
||||||
AppConfig.PREF_LANGUAGE,
|
AppConfig.PREF_LANGUAGE,
|
||||||
AppConfig.PREF_UI_MODE_NIGHT,
|
AppConfig.PREF_UI_MODE_NIGHT,
|
||||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||||
|
|
|
@ -111,49 +111,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="@dimen/padding_spacing_dp16">
|
android:paddingTop="@dimen/padding_spacing_dp16">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layout_check_update"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center|start"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="@dimen/padding_spacing_dp16">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
|
||||||
app:srcCompat="@drawable/ic_check_update_24dp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="@dimen/padding_spacing_dp16">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/update_check_for_update"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
|
||||||
android:id="@+id/check_pre_release"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/padding_spacing_dp16"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:text="@string/update_check_pre_release"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:textColor="@color/colorAccent"
|
|
||||||
app:theme="@style/BrandedSwitch" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_soure_ccode"
|
android:id="@+id/layout_soure_ccode"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
82
V2rayNG/app/src/main/res/layout/activity_check_update.xml
Normal file
82
V2rayNG/app/src/main/res/layout/activity_check_update.xml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/padding_spacing_dp16">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
app:srcCompat="@drawable/ic_source_code_24dp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/check_pre_release"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingStart="@dimen/padding_spacing_dp16"
|
||||||
|
android:text="@string/update_check_pre_release"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textColor="@color/colorAccent"
|
||||||
|
app:theme="@style/BrandedSwitch" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_check_update"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center|start"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/padding_spacing_dp16">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
app:srcCompat="@drawable/ic_check_update_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/padding_spacing_dp16"
|
||||||
|
android:text="@string/update_check_for_update"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/padding_spacing_dp16">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_version"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/title_about"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
|
@ -138,6 +138,28 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/padding_spacing_dp16"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:text="@string/sub_allow_insecure_url" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/allow_insecure_url"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/padding_spacing_dp16"
|
||||||
|
android:paddingEnd="@dimen/padding_spacing_dp16"
|
||||||
|
app:theme="@style/BrandedSwitch" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -15,97 +15,144 @@
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:nextFocusRight="@+id/layout_edit"
|
android:nextFocusRight="@+id/layout_share"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="@dimen/padding_spacing_dp8">
|
android:padding="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:orientation="vertical">
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/padding_spacing_dp8">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_url"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
|
||||||
android:lines="2"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/padding_spacing_dp8">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_share"
|
android:layout_width="0dp"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:layout_weight="1"
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="@dimen/padding_spacing_dp8">
|
android:paddingStart="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
android:id="@+id/tv_name"
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
android:layout_width="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_share_24dp" />
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:minLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_edit"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:nextFocusLeft="@+id/info_container"
|
android:orientation="horizontal">
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/padding_spacing_dp8">
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_share"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:nextFocusLeft="@+id/info_container"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
app:srcCompat="@drawable/ic_share_24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_edit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
app:srcCompat="@drawable/ic_edit_24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_remove"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/image_size_dp24"
|
||||||
|
android:layout_height="@dimen/image_size_dp24"
|
||||||
|
app:srcCompat="@drawable/ic_delete_24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="@dimen/image_size_dp24"
|
|
||||||
android:layout_height="@dimen/image_size_dp24"
|
|
||||||
app:srcCompat="@drawable/ic_edit_24dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/layout_url"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
android:orientation="horizontal"
|
||||||
android:orientation="horizontal">
|
android:paddingStart="@dimen/padding_spacing_dp8"
|
||||||
|
android:paddingEnd="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
<LinearLayout
|
||||||
android:id="@+id/chk_enable"
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="@dimen/padding_spacing_dp8">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_url"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:theme="@style/BrandedSwitch" />
|
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/chk_enable"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:theme="@style/BrandedSwitch" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -35,6 +35,10 @@
|
||||||
android:id="@+id/logcat"
|
android:id="@+id/logcat"
|
||||||
android:icon="@drawable/ic_logcat_24dp"
|
android:icon="@drawable/ic_logcat_24dp"
|
||||||
android:title="@string/title_logcat" />
|
android:title="@string/title_logcat" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/check_for_update"
|
||||||
|
android:icon="@drawable/ic_check_update_24dp"
|
||||||
|
android:title="@string/update_check_for_update" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/about"
|
android:id="@+id/about"
|
||||||
android:icon="@drawable/ic_about_24dp"
|
android:icon="@drawable/ic_about_24dp"
|
||||||
|
|
|
@ -141,6 +141,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">الإعدادات</string>
|
<string name="title_settings">الإعدادات</string>
|
||||||
<string name="title_advanced">إعدادات متقدمة</string>
|
<string name="title_advanced">إعدادات متقدمة</string>
|
||||||
|
<string name="title_core_settings">إعدادات النواة</string>
|
||||||
<string name="title_vpn_settings">إعدادات VPN</string>
|
<string name="title_vpn_settings">إعدادات VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
|
<string name="title_pref_per_app_proxy">الوكيل لكل تطبيق</string>
|
||||||
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
|
<string name="summary_pref_per_app_proxy">عام: التطبيق المحدد هو وكيل، غير المحدد اتصال مباشر؛ \nوضع التجاوز: التطبيق المحدد متصل مباشرة، غير المحدد وكيل. \nخيار تحديد تطبيق الوكيل تلقائيًا في القائمة</string>
|
||||||
|
@ -181,6 +182,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (IPv4/v6 فقط)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
<string name="title_pref_domestic_dns">DNS المحلي (اختياري)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -238,6 +241,7 @@
|
||||||
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string>
|
<string name="title_pref_auto_update_interval">فاصل التحديث التلقائي (بالدقائق، الحد الأدنى للقيمة 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">مستوى السجل</string>
|
<string name="title_core_loglevel">مستوى السجل</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">الوضع</string>
|
<string name="title_mode">الوضع</string>
|
||||||
<string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string>
|
<string name="title_mode_help">انقر هنا للحصول على مزيد من المساعدة</string>
|
||||||
<string name="title_language">اللغة</string>
|
<string name="title_language">اللغة</string>
|
||||||
|
@ -258,6 +262,7 @@
|
||||||
<string name="sub_setting_filter">Remarks regular filter</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_allow_insecure_url">Allow insecure HTTP address</string>
|
||||||
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
||||||
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
||||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||||
|
@ -315,6 +320,7 @@
|
||||||
<string name="update_new_version_found">New version found: %s</string>
|
<string name="update_new_version_found">New version found: %s</string>
|
||||||
<string name="update_now">Update now</string>
|
<string name="update_now">Update now</string>
|
||||||
<string name="update_check_pre_release">Check Pre-release</string>
|
<string name="update_check_pre_release">Check Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">Checking for update…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>رمز استجابة سريعة (QRcode)</item>
|
<item>رمز استجابة سريعة (QRcode)</item>
|
||||||
|
@ -352,4 +358,10 @@
|
||||||
<item>Not Bypass</item>
|
<item>Not Bypass</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">সেটিংস</string>
|
<string name="title_settings">সেটিংস</string>
|
||||||
<string name="title_advanced">এডভান্সড সেটিংস</string>
|
<string name="title_advanced">এডভান্সড সেটিংস</string>
|
||||||
|
<string name="title_core_settings">কোর সেটিংস</string>
|
||||||
<string name="title_vpn_settings">VPN সেটিংস</string>
|
<string name="title_vpn_settings">VPN সেটিংস</string>
|
||||||
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
|
<string name="title_pref_per_app_proxy">প্রতি-অ্যাপ প্রক্সি</string>
|
||||||
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
|
<string name="summary_pref_per_app_proxy">সাধারণ: চেকড অ্যাপ প্রক্সি, আনচেকড সরাসরি সংযোগ; \nবাইপাস মোড: চেকড অ্যাপ সরাসরি সংযুক্ত, আনচেকড প্রক্সি। \nমেনুতে প্রক্সি অ্যাপ্লিকেশন স্বয়ংক্রিয়ভাবে নির্বাচন করার বিকল্প</string>
|
||||||
|
@ -181,6 +182,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (শুধুমাত্র IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
|
<string name="title_pref_domestic_dns">ঘরোয়া DNS (ঐচ্ছিক)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -238,6 +241,7 @@
|
||||||
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string>
|
<string name="title_pref_auto_update_interval">অটো আপডেট ইন্টারভ্যাল (মিনিট, সর্বনিম্ন মান ১৫)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">লগ স্তর</string>
|
<string name="title_core_loglevel">লগ স্তর</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">মোড</string>
|
<string name="title_mode">মোড</string>
|
||||||
<string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
|
<string name="title_mode_help">আরো সাহায্যের জন্য ক্লিক করুন</string>
|
||||||
<string name="title_language">ভাষা</string>
|
<string name="title_language">ভাষা</string>
|
||||||
|
@ -258,6 +262,7 @@
|
||||||
<string name="sub_setting_filter">Remarks regular filter</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_allow_insecure_url">Allow insecure HTTP address</string>
|
||||||
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
||||||
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
||||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||||
|
@ -314,6 +319,7 @@
|
||||||
<string name="update_new_version_found">New version found: %s</string>
|
<string name="update_new_version_found">New version found: %s</string>
|
||||||
<string name="update_now">Update now</string>
|
<string name="update_now">Update now</string>
|
||||||
<string name="update_check_pre_release">Check Pre-release</string>
|
<string name="update_check_pre_release">Check Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">Checking for update…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QR কোড</item>
|
<item>QR কোড</item>
|
||||||
|
@ -357,4 +363,10 @@
|
||||||
<item>Not Bypass</item>
|
<item>Not Bypass</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -40,7 +40,7 @@
|
||||||
<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">نوم من توری</string>
|
<string name="server_lab_id">نوم منتوری</string>
|
||||||
<string name="server_lab_alterid">شناسه جایگۊزین</string>
|
<string name="server_lab_alterid">شناسه جایگۊزین</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>
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
<string name="server_lab_id3">رزم</string>
|
<string name="server_lab_id3">رزم</string>
|
||||||
<string name="server_lab_security3">ٱمنیت</string>
|
<string name="server_lab_security3">ٱمنیت</string>
|
||||||
<string name="server_lab_id4">رزم (اختیاری)</string>
|
<string name="server_lab_id4">رزم (اختیاری)</string>
|
||||||
<string name="server_lab_security4">نوم من توری (اختیاری)</string>
|
<string name="server_lab_security4">نوم منتوری (اختیاری)</string>
|
||||||
<string name="server_lab_encryption">رزم نگاری</string>
|
<string name="server_lab_encryption">رزم نگاری</string>
|
||||||
<string name="server_lab_flow">جریان</string>
|
<string name="server_lab_flow">جریان</string>
|
||||||
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
|
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
|
||||||
|
@ -140,6 +140,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">سامووا</string>
|
<string name="title_settings">سامووا</string>
|
||||||
<string name="title_advanced">سامووا پؽش رئڌه</string>
|
<string name="title_advanced">سامووا پؽش رئڌه</string>
|
||||||
|
<string name="title_core_settings">سامووا هسته</string>
|
||||||
<string name="title_vpn_settings">سامووا VPN</string>
|
<string name="title_vpn_settings">سامووا VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
|
<string name="title_pref_per_app_proxy">پروکسی و ری برنومه</string>
|
||||||
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string>
|
<string name="summary_pref_per_app_proxy">پوی وولاتی: برنومه واجۊری بیڌه پروکسی هڌ، منپیز موستقیم بؽ نشووه هڌ. هالت دور زیڌن: برنومه نشووک ناڌه موستقیمن منپیز هڌ، پروکسی نشووک زیڌه نؽڌ. گۊزینه پسند خوتکار برنومه پروکسی من نومگه</string>
|
||||||
|
@ -148,7 +149,7 @@
|
||||||
|
|
||||||
<string name="title_mux_settings">سامووا Mux</string>
|
<string name="title_mux_settings">سامووا Mux</string>
|
||||||
<string name="title_pref_mux_enabled">ر وندن Mux</string>
|
<string name="title_pref_mux_enabled">ر وندن Mux</string>
|
||||||
<string name="summary_pref_mux_enabled">زل تر، ٱما گاشڌ منپیز زی قت بۊ بارت دؽوۉداری، TCP، UDP و QUIC ن ای لم سفارشی کۊنین.</string>
|
<string name="summary_pref_mux_enabled">زل تر، ٱما گاشڌ منپیز زی قت بۊ\nمخزن ترافیک TCP وا 8 منپیز پؽش فرز، بارت دؽوۉداری UDP وو QUIC ن ای لم سفارشی کۊنین.</string>
|
||||||
<string name="title_pref_mux_concurency">منپیزا TCP (تلایه منجا 1-1024)</string>
|
<string name="title_pref_mux_concurency">منپیزا TCP (تلایه منجا 1-1024)</string>
|
||||||
<string name="title_pref_mux_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string>
|
<string name="title_pref_mux_xudp_concurency">منپیزا XUDP (تلایه منجا 1-1024)</string>
|
||||||
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string>
|
<string name="title_pref_mux_xudp_quic">دؽوۉداری QUIC من تۊنل mux</string>
|
||||||
|
@ -167,24 +168,26 @@
|
||||||
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string>
|
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
|
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS پردازشت وابیڌه و دس هسته ماژول DNS (پؽشنهاڌ ابۊ، ٱر نیاز هڌ ک جوستن تور وو ولات ٱسلین دور زنی)</string>
|
<string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین فعال بۊ)</string>
|
||||||
|
|
||||||
<string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string>
|
<string name="title_pref_fake_dns_enabled">ر وندن DNS جئلی</string>
|
||||||
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
|
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
|
||||||
|
|
||||||
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
|
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
|
||||||
<string name="summary_pref_prefer_ipv6">ترجی داڌن نشۊوی وو تورا IPv6</string>
|
<string name="summary_pref_prefer_ipv6">تورا IPv6 ن فعال کۊنین وو نشۊویا IPv6 ن ترجی بڌین</string>
|
||||||
|
|
||||||
<string name="title_pref_remote_dns">DNS ز ر دیر (اختیاری) (udp/tcp/https/quic) (اختیاری)</string>
|
<string name="title_pref_remote_dns">ز ر دیر (اختیاری) DNS (udp/tcp/https/quic) (اختیاری)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
|
<string name="title_pref_vpn_bypass_lan">ز شبکه مهلی اگوڌرته؟ VPN</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
|
<string name="title_pref_vpn_interface_address">نشۊوی رابت VPN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_domestic_dns">منی (اختیاری) DNS</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: نشۊوی،...)</string>
|
<string name="title_pref_dns_hosts">هاست موستقیم (قالوو: دامنه: نشۊوی،...) DNS</string>
|
||||||
<string name="summary_pref_dns_hosts">دامنه:نشۊوی،...</string>
|
<string name="summary_pref_dns_hosts">دامنه:نشۊوی،...</string>
|
||||||
|
|
||||||
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی (http/https)</string>
|
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی (http/https)</string>
|
||||||
|
@ -204,20 +207,20 @@
|
||||||
<string name="summary_pref_local_dns_port">پورت DNS مهلی</string>
|
<string name="summary_pref_local_dns_port">پورت DNS مهلی</string>
|
||||||
|
|
||||||
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
|
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
|
||||||
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ</string>
|
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ نیاز به قوۊل کردن دووارته ز سمت منتور هڌ.</string>
|
||||||
|
|
||||||
<string name="title_pref_start_scan_immediate">زی اسکنن ر ون</string>
|
<string name="title_pref_start_scan_immediate">زی اسکنن ر ون</string>
|
||||||
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، اندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
|
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ر وندن بۊگۊشین، ٱندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
|
||||||
|
|
||||||
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
|
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
|
||||||
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
|
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
|
||||||
|
|
||||||
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
|
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
|
||||||
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن وا برنومه ن ز نۊ ر ونین.</string>
|
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
|
||||||
|
|
||||||
<!-- AboutActivity -->
|
<!-- AboutActivity -->
|
||||||
<string name="title_pref_feedback">فشناڌن منشڌ</string>
|
<string name="title_pref_feedback">فشناڌن منشڌ</string>
|
||||||
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسوو موشکلا من Github</string>
|
<string name="summary_pref_feedback">فشناڌن منشڌ یا داسووݩ موشکلا من Github</string>
|
||||||
<string name="summary_pref_tg_group">ٱووڌن من جرگه تلگرام</string>
|
<string name="summary_pref_tg_group">ٱووڌن من جرگه تلگرام</string>
|
||||||
<string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string>
|
<string name="toast_tg_app_not_found">برنومه تلگرامن نجوست</string>
|
||||||
<string name="title_privacy_policy">هریم سیخومی</string>
|
<string name="title_privacy_policy">هریم سیخومی</string>
|
||||||
|
@ -238,6 +241,7 @@
|
||||||
<string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string>
|
<string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">سئت داسووا</string>
|
<string name="title_core_loglevel">سئت داسووا</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">بارت پؽش هل دامنه دری</string>
|
||||||
<string name="title_mode">هالت</string>
|
<string name="title_mode">هالت</string>
|
||||||
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
|
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
|
||||||
<string name="title_language">زۉݩ</string>
|
<string name="title_language">زۉݩ</string>
|
||||||
|
@ -258,6 +262,7 @@
|
||||||
<string name="sub_setting_filter">نوم موستعار فیلتر</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_allow_insecure_url">موجاز کردن نشۊوی HTTP نا ٱمن</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>
|
||||||
|
@ -303,7 +308,7 @@
|
||||||
<string name="connection_test_pending">منپیزن واجۊری کوݩ</string>
|
<string name="connection_test_pending">منپیزن واجۊری کوݩ</string>
|
||||||
<string name="connection_test_testing">هونی آزمایش ابۊ…</string>
|
<string name="connection_test_testing">هونی آزمایش ابۊ…</string>
|
||||||
<string name="connection_test_testing_count">%d کانفیگ هونی آزمایش ابۊ...</string>
|
<string name="connection_test_testing_count">%d کانفیگ هونی آزمایش ابۊ...</string>
|
||||||
<string name="connection_test_available">مووفق بی: منپیز HTTP %dms تۊل کشی</string>
|
<string name="connection_test_available">مووفق بی: منپیز %dms تۊل کشی</string>
|
||||||
<string name="connection_test_error">منپیز و اینترنتن نجوست: %s</string>
|
<string name="connection_test_error">منپیز و اینترنتن نجوست: %s</string>
|
||||||
<string name="connection_test_fail">اینترنت من دسرس نؽ</string>
|
<string name="connection_test_fail">اینترنت من دسرس نؽ</string>
|
||||||
<string name="connection_test_error_status_code">کود ختا: #%d</string>
|
<string name="connection_test_error_status_code">کود ختا: #%d</string>
|
||||||
|
@ -322,7 +327,8 @@
|
||||||
<string name="update_already_latest_version">سکو نوسخه دیندایی پۊرنیڌه هڌ</string>
|
<string name="update_already_latest_version">سکو نوسخه دیندایی پۊرنیڌه هڌ</string>
|
||||||
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
|
<string name="update_new_version_found">نوسخه نۊ ن جوست: %s</string>
|
||||||
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
|
<string name="update_now">سکو ورۊ رسۊوی کۊنین</string>
|
||||||
<string name="update_check_pre_release">نوسخیل پؽش ز تیجنیڌنن واجۊری کۊنین</string>
|
<string name="update_check_pre_release">واجۊری نوسخه یل پؽش ز تیجنیڌن</string>
|
||||||
|
<string name="update_checking_for_update">ورۊ رسۊوی ن هونی واجۊری اکونه...</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QRcode</item>
|
<item>QRcode</item>
|
||||||
|
@ -367,4 +373,10 @@
|
||||||
<item>دور زیڌه نبۊ</item>
|
<item>دور زیڌه نبۊ</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>هل وو فسل مکۊنین</item>
|
||||||
|
<item>هل وو ٱووردن و میزبووݩ یل دامنه DNS</item>
|
||||||
|
<item>هل وو جایونی دامنه</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -137,6 +137,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">تنظیمات</string>
|
<string name="title_settings">تنظیمات</string>
|
||||||
<string name="title_advanced">تنظیمات پیشرفته</string>
|
<string name="title_advanced">تنظیمات پیشرفته</string>
|
||||||
|
<string name="title_core_settings">تنظیمات هسته</string>
|
||||||
<string name="title_vpn_settings">تنظیمات VPN</string>
|
<string name="title_vpn_settings">تنظیمات VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
|
<string name="title_pref_per_app_proxy">پروکسی به تفکیک برنامه</string>
|
||||||
<string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string>
|
<string name="summary_pref_per_app_proxy">عمومی: برنامه انتخاب شده از طریق یک پروکسی متصل می شود، برنامه انتخاب نشده مستقیماً متصل می شود. \nحالت دور زدن: برنامه انتخاب شده مستقیماً متصل می شود، برنامه انتخاب نشده از طریق یک پروکسی متصل می شود. \nانتخاب خودکار برنامه های پراکسی در منو امکان پذیر است.</string>
|
||||||
|
@ -171,7 +172,7 @@
|
||||||
<string name="summary_pref_fake_dns_enabled">دی ان اس محلی آدرس های آیپی جعلی را بر می گرداند (سریع تر می باشد و تاخیر را کاهش می دهد اما ممکن است برای برخی از برنامه ها کار نکند)</string>
|
<string name="summary_pref_fake_dns_enabled">دی ان اس محلی آدرس های آیپی جعلی را بر می گرداند (سریع تر می باشد و تاخیر را کاهش می دهد اما ممکن است برای برخی از برنامه ها کار نکند)</string>
|
||||||
|
|
||||||
<string name="title_pref_prefer_ipv6">ترجیح دادن IPV6</string>
|
<string name="title_pref_prefer_ipv6">ترجیح دادن IPV6</string>
|
||||||
<string name="summary_pref_prefer_ipv6">ترجیح دادن نشانی و مسیر های IPv6</string>
|
<string name="summary_pref_prefer_ipv6">مسیرهای IPv6 را فعال کنید و آدرسهای IPv6 را ترجیح دهید</string>
|
||||||
|
|
||||||
<string name="title_pref_remote_dns">DNS از راه دور (اختیاری) (udp/tcp/https/quic)</string>
|
<string name="title_pref_remote_dns">DNS از راه دور (اختیاری) (udp/tcp/https/quic)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
@ -179,6 +180,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (فقط IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
|
<string name="title_pref_vpn_bypass_lan">آیا VPN از شبکه محلی عبور می کند؟</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
<string name="title_pref_domestic_dns">DNS داخلی (اختیاری)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -235,6 +238,7 @@
|
||||||
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
|
<string name="summary_pref_auto_update_subscription">اشتراک های خود را به طور خودکار با فاصله زمانی در پس زمینه به روز کنید. بسته به دستگاه، این ویژگی ممکن است همیشه کار نکند.</string>
|
||||||
<string name="title_pref_auto_update_interval">فاصله به روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
|
<string name="title_pref_auto_update_interval">فاصله به روزرسانی خودکار ( حداقل مقدار ، 15 دقیقه )</string>
|
||||||
<string name="title_core_loglevel">سطح گزارشات</string>
|
<string name="title_core_loglevel">سطح گزارشات</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">حالت</string>
|
<string name="title_mode">حالت</string>
|
||||||
<string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
|
<string name="title_mode_help">برای اطلاعات و راهنمایی بیشتر، روی این متن کلیک کنید</string>
|
||||||
<string name="title_language">زبان</string>
|
<string name="title_language">زبان</string>
|
||||||
|
@ -255,6 +259,7 @@
|
||||||
<string name="sub_setting_filter">نام مستعار فیلتر</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_allow_insecure_url">مجاز کردن آدرس HTTP ناامن</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>
|
||||||
|
@ -300,7 +305,7 @@
|
||||||
<string name="connection_test_pending">اتصال را بررسی کنید</string>
|
<string name="connection_test_pending">اتصال را بررسی کنید</string>
|
||||||
<string name="connection_test_testing">در حال آزمایش...</string>
|
<string name="connection_test_testing">در حال آزمایش...</string>
|
||||||
<string name="connection_test_testing_count">تست کردن %d کانفیگ…</string>
|
<string name="connection_test_testing_count">تست کردن %d کانفیگ…</string>
|
||||||
<string name="connection_test_available">موفقیت: اتصال HTTP %dms طول کشید</string>
|
<string name="connection_test_available">موفقیت: اتصال %dms طول کشید</string>
|
||||||
<string name="connection_test_error">اتصال به اینترنت شناسایی نشد: %s</string>
|
<string name="connection_test_error">اتصال به اینترنت شناسایی نشد: %s</string>
|
||||||
<string name="connection_test_fail">اینترنت در دسترس نیست</string>
|
<string name="connection_test_fail">اینترنت در دسترس نیست</string>
|
||||||
<string name="connection_test_error_status_code">کد خطا: #%d</string>
|
<string name="connection_test_error_status_code">کد خطا: #%d</string>
|
||||||
|
@ -320,6 +325,7 @@
|
||||||
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
|
<string name="update_new_version_found">نسخه جدید پیدا شد: %s</string>
|
||||||
<string name="update_now">اکنون به روز رسانی کنید</string>
|
<string name="update_now">اکنون به روز رسانی کنید</string>
|
||||||
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
|
<string name="update_check_pre_release">بررسی نسخه پیش از انتشار</string>
|
||||||
|
<string name="update_checking_for_update">در حال بررسی برای بهروزرسانی…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QRcode</item>
|
<item>QRcode</item>
|
||||||
|
@ -366,4 +372,10 @@
|
||||||
<item>دور زده نشود</item>
|
<item>دور زده نشود</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">Настройки</string>
|
<string name="title_settings">Настройки</string>
|
||||||
<string name="title_advanced">Расширенные настройки</string>
|
<string name="title_advanced">Расширенные настройки</string>
|
||||||
|
<string name="title_core_settings">Настройки ядра</string>
|
||||||
<string name="title_vpn_settings">Настройки VPN</string>
|
<string name="title_vpn_settings">Настройки VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">Прокси для выбранных приложений</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>
|
||||||
|
@ -172,7 +173,7 @@
|
||||||
<string name="summary_pref_fake_dns_enabled">Локальная DNS возвращает поддельные IP-адреса (быстрее, но может не работать с некоторыми приложениями)</string>
|
<string name="summary_pref_fake_dns_enabled">Локальная DNS возвращает поддельные IP-адреса (быстрее, но может не работать с некоторыми приложениями)</string>
|
||||||
|
|
||||||
<string name="title_pref_prefer_ipv6">Предпочитать IPv6</string>
|
<string name="title_pref_prefer_ipv6">Предпочитать IPv6</string>
|
||||||
<string name="summary_pref_prefer_ipv6">Предпочитать IPv6-адреса и маршрутизацию</string>
|
<string name="summary_pref_prefer_ipv6">Использовать маршрутизацию IPv6 предпочитать IPv6-адреса</string>
|
||||||
|
|
||||||
<string name="title_pref_remote_dns">Удалённая DNS (UDP/TCP/HTTPS/QUIC) (необязательно)</string>
|
<string name="title_pref_remote_dns">Удалённая DNS (UDP/TCP/HTTPS/QUIC) (необязательно)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
@ -180,6 +181,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string>
|
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN частный IP</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
<string name="title_pref_domestic_dns">Внутренняя DNS (необязательно)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -237,6 +240,7 @@
|
||||||
<string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string>
|
<string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">Подробность ведения журнала</string>
|
<string name="title_core_loglevel">Подробность ведения журнала</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">Режим</string>
|
<string name="title_mode">Режим</string>
|
||||||
<string name="title_mode_help">Нажмите для получения дополнительной информации</string>
|
<string name="title_mode_help">Нажмите для получения дополнительной информации</string>
|
||||||
<string name="title_language">Язык</string>
|
<string name="title_language">Язык</string>
|
||||||
|
@ -257,9 +261,10 @@
|
||||||
<string name="sub_setting_filter">Название фильтра</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_allow_insecure_url">Разрешать незащищённые HTTP-адреса</string>
|
||||||
<string name="sub_setting_next_profile">Название следующего прокси</string>
|
<string name="sub_setting_pre_profile">Предыдущая конфигурация прокси</string>
|
||||||
<string name="sub_setting_pre_profile_tip">Название должно существовать и быть уникальным</string>
|
<string name="sub_setting_next_profile">Следующая конфигурация прокси</string>
|
||||||
|
<string name="sub_setting_pre_profile_tip">Конфигурация должна быть уникальной</string>
|
||||||
<string name="title_sub_update">Обновить подписку группы</string>
|
<string name="title_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>
|
||||||
|
@ -302,7 +307,7 @@
|
||||||
<string name="connection_test_pending">Проверить подключение</string>
|
<string name="connection_test_pending">Проверить подключение</string>
|
||||||
<string name="connection_test_testing">Проверка…</string>
|
<string name="connection_test_testing">Проверка…</string>
|
||||||
<string name="connection_test_testing_count">Проверка профилей (%d)</string>
|
<string name="connection_test_testing_count">Проверка профилей (%d)</string>
|
||||||
<string name="connection_test_available">Успешно: HTTP-соединение заняло %d мс</string>
|
<string name="connection_test_available">Успешно: соединение заняло %d мс</string>
|
||||||
<string name="connection_test_error">Сбой проверки интернет-соединения: %s</string>
|
<string name="connection_test_error">Сбой проверки интернет-соединения: %s</string>
|
||||||
<string name="connection_test_fail">Интернет недоступен</string>
|
<string name="connection_test_fail">Интернет недоступен</string>
|
||||||
<string name="connection_test_error_status_code">Код ошибки: #%d</string>
|
<string name="connection_test_error_status_code">Код ошибки: #%d</string>
|
||||||
|
@ -321,7 +326,8 @@
|
||||||
<string name="update_already_latest_version">Установлена последняя версия</string>
|
<string name="update_already_latest_version">Установлена последняя версия</string>
|
||||||
<string name="update_new_version_found">Найдена новая версия: %s</string>
|
<string name="update_new_version_found">Найдена новая версия: %s</string>
|
||||||
<string name="update_now">Обновить</string>
|
<string name="update_now">Обновить</string>
|
||||||
<string name="update_check_pre_release">Проверить предварительный выпуск</string>
|
<string name="update_check_pre_release">Искать предварительный выпуск</string>
|
||||||
|
<string name="update_checking_for_update">Проверка обновления…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QR-код</item>
|
<item>QR-код</item>
|
||||||
|
@ -366,4 +372,10 @@
|
||||||
<item>Не пропускает</item>
|
<item>Не пропускает</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -138,6 +138,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">Cài đặt</string>
|
<string name="title_settings">Cài đặt</string>
|
||||||
<string name="title_advanced">Cài đặt nâng cao</string>
|
<string name="title_advanced">Cài đặt nâng cao</string>
|
||||||
|
<string name="title_core_settings">Cài đặt lõi</string>
|
||||||
<string name="title_vpn_settings">Cài đặt VPN</string>
|
<string name="title_vpn_settings">Cài đặt VPN</string>
|
||||||
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
|
<string name="title_pref_per_app_proxy">Proxy theo Ứng dụng</string>
|
||||||
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
|
<string name="summary_pref_per_app_proxy">- Bình thường: Ứng dụng đã chọn sẽ kết nối thông qua Proxy, chưa chọn sẽ kết nối trực tiếp. \n- Chế độ Bypass: Ứng dụng đã chọn sẽ kết nối trực tiếp, chưa chọn sẽ kết nối qua Proxy. \n- Nếu bạn đang ở Trung Quốc thì vào Menu, chọn Tự động chọn ứng dụng Proxy.</string>
|
||||||
|
@ -181,6 +182,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4 / IPv6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
|
<string name="title_pref_domestic_dns">DNS nội địa (Không bắt buộc)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -238,6 +241,7 @@
|
||||||
<string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string>
|
<string name="title_pref_auto_update_interval">Thời gian cập nhật tự động (Phút, giá trị tối thiểu là 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">Cấp độ nhật ký</string>
|
<string name="title_core_loglevel">Cấp độ nhật ký</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">Chế độ kết nối</string>
|
<string name="title_mode">Chế độ kết nối</string>
|
||||||
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
|
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp!</string>
|
||||||
<string name="title_language">Ngôn ngữ</string>
|
<string name="title_language">Ngôn ngữ</string>
|
||||||
|
@ -258,6 +262,7 @@
|
||||||
<string name="sub_setting_filter">Remarks regular filter</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_allow_insecure_url">Allow insecure HTTP address</string>
|
||||||
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
||||||
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
||||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||||
|
@ -316,6 +321,7 @@
|
||||||
<string name="update_new_version_found">New version found: %s</string>
|
<string name="update_new_version_found">New version found: %s</string>
|
||||||
<string name="update_now">Update now</string>
|
<string name="update_now">Update now</string>
|
||||||
<string name="update_check_pre_release">Check Pre-release</string>
|
<string name="update_check_pre_release">Check Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">Checking for update…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
||||||
|
@ -354,4 +360,10 @@
|
||||||
<item>Not Bypass</item>
|
<item>Not Bypass</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -137,6 +137,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">设置</string>
|
<string name="title_settings">设置</string>
|
||||||
<string name="title_advanced">进阶设置</string>
|
<string name="title_advanced">进阶设置</string>
|
||||||
|
<string name="title_core_settings">核心设置</string>
|
||||||
<string name="title_vpn_settings">VPN 设置</string>
|
<string name="title_vpn_settings">VPN 设置</string>
|
||||||
<string name="title_pref_per_app_proxy">分应用</string>
|
<string name="title_pref_per_app_proxy">分应用</string>
|
||||||
<string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
|
<string name="summary_pref_per_app_proxy">常规: 勾选的 App 被代理, 未勾选的直连;\n绕行模式: 勾选的 App 直连, 未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
|
||||||
|
@ -178,6 +179,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string>
|
<string name="title_pref_vpn_bypass_lan">VPN 是否绕过局域网</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN 接口地址</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
|
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -235,6 +238,7 @@
|
||||||
<string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15)</string>
|
<string name="title_pref_auto_update_interval">自动更新间隔(分钟,最小值 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">日志级别</string>
|
<string name="title_core_loglevel">日志级别</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound 域名预解析方式</string>
|
||||||
<string name="title_mode">模式</string>
|
<string name="title_mode">模式</string>
|
||||||
<string name="title_mode_help">点此查看更多帮助</string>
|
<string name="title_mode_help">点此查看更多帮助</string>
|
||||||
<string name="title_language">语言</string>
|
<string name="title_language">语言</string>
|
||||||
|
@ -255,6 +259,7 @@
|
||||||
<string name="sub_setting_filter">别名正则过滤</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_allow_insecure_url">允许不安全的 HTTP 地址</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>
|
||||||
|
@ -314,6 +319,7 @@
|
||||||
<string name="update_new_version_found">发现新版本: %s</string>
|
<string name="update_new_version_found">发现新版本: %s</string>
|
||||||
<string name="update_now">立即更新</string>
|
<string name="update_now">立即更新</string>
|
||||||
<string name="update_check_pre_release">检查 Pre-release</string>
|
<string name="update_check_pre_release">检查 Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">正在检查更新中…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>二维码</item>
|
<item>二维码</item>
|
||||||
|
@ -358,4 +364,10 @@
|
||||||
<item>不绕过</item>
|
<item>不绕过</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>不解析</item>
|
||||||
|
<item>解析后添加至 DNS Hosts</item>
|
||||||
|
<item>解析后替换原域名</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -138,6 +138,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">設定</string>
|
<string name="title_settings">設定</string>
|
||||||
<string name="title_advanced">進階</string>
|
<string name="title_advanced">進階</string>
|
||||||
|
<string name="title_core_settings">核心設定</string>
|
||||||
<string name="title_vpn_settings">VPN 設定</string>
|
<string name="title_vpn_settings">VPN 設定</string>
|
||||||
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
||||||
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy,未勾選的直接連線;\n繞行模式:勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
|
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy,未勾選的直接連線;\n繞行模式:勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
|
||||||
|
@ -180,6 +181,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string>
|
<string name="title_pref_vpn_bypass_lan">VPN 是否繞過區域網</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN 介面位址</string>
|
||||||
|
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
|
<string name="title_pref_domestic_dns">境内 DNS (可选)</string>
|
||||||
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
|
<string name="title_pref_dns_hosts">DNS hosts (格式: 網域:位址,…)</string>
|
||||||
|
@ -236,6 +239,7 @@
|
||||||
<string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15)</string>
|
<string name="title_pref_auto_update_interval">自動更新間隔(分鐘,最小值 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">記錄層級</string>
|
<string name="title_core_loglevel">記錄層級</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound 網域預解析方式</string>
|
||||||
<string name="title_mode">模式</string>
|
<string name="title_mode">模式</string>
|
||||||
<string name="title_mode_help">輕觸以檢視說明</string>
|
<string name="title_mode_help">輕觸以檢視說明</string>
|
||||||
<string name="title_language">語言</string>
|
<string name="title_language">語言</string>
|
||||||
|
@ -256,6 +260,7 @@
|
||||||
<string name="sub_setting_filter">別名正規過濾</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_allow_insecure_url">允許不安全的 HTTP 位址</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>
|
||||||
|
@ -314,6 +319,7 @@
|
||||||
<string name="update_new_version_found">發現新版本: %s</string>
|
<string name="update_new_version_found">發現新版本: %s</string>
|
||||||
<string name="update_now">立即更新</string>
|
<string name="update_now">立即更新</string>
|
||||||
<string name="update_check_pre_release">檢查 Pre-release</string>
|
<string name="update_check_pre_release">檢查 Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">正在檢查更新中…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QR Code</item>
|
<item>QR Code</item>
|
||||||
|
@ -358,4 +364,10 @@
|
||||||
<item>不繞過</item>
|
<item>不繞過</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>不解析</item>
|
||||||
|
<item>解析後加入 DNS Hosts</item>
|
||||||
|
<item>解析後替換原網域名稱</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -182,4 +182,30 @@
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_interface_address_value" translatable="false">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="vpn_interface_address">
|
||||||
|
<item>10.10.14.x</item>
|
||||||
|
<item>10.1.0.x</item>
|
||||||
|
<item>10.0.0.x</item>
|
||||||
|
<item>172.31.0.x</item>
|
||||||
|
<item>172.20.0.x</item>
|
||||||
|
<item>172.16.0.x</item>
|
||||||
|
<item>192.168.100.x</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method_value" translatable="false">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -140,6 +140,7 @@
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">Settings</string>
|
<string name="title_settings">Settings</string>
|
||||||
<string name="title_advanced">Advanced Settings</string>
|
<string name="title_advanced">Advanced Settings</string>
|
||||||
|
<string name="title_core_settings">Core Settings</string>
|
||||||
<string name="title_vpn_settings">VPN Settings</string>
|
<string name="title_vpn_settings">VPN Settings</string>
|
||||||
<string name="title_pref_per_app_proxy">Per-app proxy</string>
|
<string name="title_pref_per_app_proxy">Per-app proxy</string>
|
||||||
<string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string>
|
<string name="summary_pref_per_app_proxy">General: Checked apps use proxy, unchecked apps connect directly; \nBypass mode: checked apps connect directly, unchecked apps use proxy. \nThe option to automatically select proxy applications is in the menu</string>
|
||||||
|
@ -174,7 +175,7 @@
|
||||||
<string name="summary_pref_fake_dns_enabled">Local DNS returns fake IP addresses (faster, but it may not work for some apps)</string>
|
<string name="summary_pref_fake_dns_enabled">Local DNS returns fake IP addresses (faster, but it may not work for some apps)</string>
|
||||||
|
|
||||||
<string name="title_pref_prefer_ipv6">Prefer IPv6</string>
|
<string name="title_pref_prefer_ipv6">Prefer IPv6</string>
|
||||||
<string name="summary_pref_prefer_ipv6">Prefer IPv6 addresses and routes</string>
|
<string name="summary_pref_prefer_ipv6">Enable IPv6 routes and Prefer IPv6 addresses</string>
|
||||||
|
|
||||||
<string name="title_pref_remote_dns">Remote DNS (udp/tcp/https/quic)(Optional)</string>
|
<string name="title_pref_remote_dns">Remote DNS (udp/tcp/https/quic)(Optional)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
@ -182,6 +183,8 @@
|
||||||
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
|
||||||
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
<string name="title_pref_vpn_bypass_lan">Does VPN bypass LAN</string>
|
||||||
|
|
||||||
|
<string name="title_pref_vpn_interface_address">VPN Interface Address</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
|
@ -239,6 +242,7 @@
|
||||||
<string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string>
|
<string name="title_pref_auto_update_interval">Auto Update Interval (Minutes, Min value 15)</string>
|
||||||
|
|
||||||
<string name="title_core_loglevel">Log Level</string>
|
<string name="title_core_loglevel">Log Level</string>
|
||||||
|
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
|
||||||
<string name="title_mode">Mode</string>
|
<string name="title_mode">Mode</string>
|
||||||
<string name="title_mode_help">Click me for more help</string>
|
<string name="title_mode_help">Click me for more help</string>
|
||||||
<string name="title_language">Language</string>
|
<string name="title_language">Language</string>
|
||||||
|
@ -259,6 +263,7 @@
|
||||||
<string name="sub_setting_filter">Remarks regular filter</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_allow_insecure_url">Allow insecure HTTP address</string>
|
||||||
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
<string name="sub_setting_pre_profile">Previous proxy configuration remarks</string>
|
||||||
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
<string name="sub_setting_next_profile">Next proxy configuration remarks</string>
|
||||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||||
|
@ -304,7 +309,7 @@
|
||||||
<string name="connection_test_pending">Check Connectivity</string>
|
<string name="connection_test_pending">Check Connectivity</string>
|
||||||
<string name="connection_test_testing">Testing…</string>
|
<string name="connection_test_testing">Testing…</string>
|
||||||
<string name="connection_test_testing_count">Testing %d configurations…</string>
|
<string name="connection_test_testing_count">Testing %d configurations…</string>
|
||||||
<string name="connection_test_available">Success: HTTP connection took %dms</string>
|
<string name="connection_test_available">Success: Connection took %dms</string>
|
||||||
<string name="connection_test_error">Fail to detect internet connection: %s</string>
|
<string name="connection_test_error">Fail to detect internet connection: %s</string>
|
||||||
<string name="connection_test_fail">Internet Unavailable</string>
|
<string name="connection_test_fail">Internet Unavailable</string>
|
||||||
<string name="connection_test_error_status_code">Error code: #%d</string>
|
<string name="connection_test_error_status_code">Error code: #%d</string>
|
||||||
|
@ -324,6 +329,7 @@
|
||||||
<string name="update_new_version_found">New version found: %s</string>
|
<string name="update_new_version_found">New version found: %s</string>
|
||||||
<string name="update_now">Update now</string>
|
<string name="update_now">Update now</string>
|
||||||
<string name="update_check_pre_release">Check Pre-release</string>
|
<string name="update_check_pre_release">Check Pre-release</string>
|
||||||
|
<string name="update_checking_for_update">Checking for update…</string>
|
||||||
|
|
||||||
<string-array name="share_method">
|
<string-array name="share_method">
|
||||||
<item>QRcode</item>
|
<item>QRcode</item>
|
||||||
|
@ -368,4 +374,10 @@
|
||||||
<item>Not Bypass</item>
|
<item>Not Bypass</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="outbound_domain_resolve_method">
|
||||||
|
<item>Do not resolve</item>
|
||||||
|
<item>Resolve and add to DNS Hosts</item>
|
||||||
|
<item>Resolve and replace domain</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,6 +19,11 @@
|
||||||
android:title="@string/title_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
|
||||||
|
android:key="pref_prefer_ipv6"
|
||||||
|
android:summary="@string/summary_pref_prefer_ipv6"
|
||||||
|
android:title="@string/title_pref_prefer_ipv6" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="pref_per_app_proxy"
|
android:key="pref_per_app_proxy"
|
||||||
android:summary="@string/summary_pref_per_app_proxy"
|
android:summary="@string/summary_pref_per_app_proxy"
|
||||||
|
@ -51,12 +56,20 @@
|
||||||
android:title="@string/title_pref_vpn_dns" />
|
android:title="@string/title_pref_vpn_dns" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="0"
|
android:defaultValue="1"
|
||||||
android:entries="@array/vpn_bypass_lan"
|
android:entries="@array/vpn_bypass_lan"
|
||||||
android:entryValues="@array/vpn_bypass_lan_value"
|
android:entryValues="@array/vpn_bypass_lan_value"
|
||||||
android:key="pref_vpn_bypass_lan"
|
android:key="pref_vpn_bypass_lan"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/title_pref_vpn_bypass_lan" />
|
android:title="@string/title_pref_vpn_bypass_lan" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/vpn_interface_address"
|
||||||
|
android:entryValues="@array/vpn_interface_address_value"
|
||||||
|
android:key="pref_vpn_interface_address_config_index"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/title_pref_vpn_interface_address" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/title_ui_settings">
|
<PreferenceCategory android:title="@string/title_ui_settings">
|
||||||
|
@ -166,14 +179,7 @@
|
||||||
android:title="@string/title_pref_auto_update_interval" />
|
android:title="@string/title_pref_auto_update_interval" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory android:title="@string/title_core_settings">
|
||||||
android:title="@string/title_advanced"
|
|
||||||
app:initialExpandedChildrenCount="0">
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="pref_prefer_ipv6"
|
|
||||||
android:summary="@string/summary_pref_prefer_ipv6"
|
|
||||||
android:title="@string/title_pref_prefer_ipv6" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
|
@ -208,11 +214,6 @@
|
||||||
android:summary="@string/summary_pref_dns_hosts"
|
android:summary="@string/summary_pref_dns_hosts"
|
||||||
android:title="@string/title_pref_dns_hosts" />
|
android:title="@string/title_pref_dns_hosts" />
|
||||||
|
|
||||||
<EditTextPreference
|
|
||||||
android:key="pref_delay_test_url"
|
|
||||||
android:summary="@string/summary_pref_delay_test_url"
|
|
||||||
android:title="@string/title_pref_delay_test_url" />
|
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="warning"
|
android:defaultValue="warning"
|
||||||
android:entries="@array/core_loglevel"
|
android:entries="@array/core_loglevel"
|
||||||
|
@ -221,6 +222,23 @@
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/title_core_loglevel" />
|
android:title="@string/title_core_loglevel" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="1"
|
||||||
|
android:entries="@array/outbound_domain_resolve_method"
|
||||||
|
android:entryValues="@array/outbound_domain_resolve_method_value"
|
||||||
|
android:key="pref_outbound_domain_resolve_method"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/title_outbound_domain_resolve_method" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="@string/title_advanced">
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="pref_delay_test_url"
|
||||||
|
android:summary="@string/summary_pref_delay_test_url"
|
||||||
|
android:title="@string/title_pref_delay_test_url" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="VPN"
|
android:defaultValue="VPN"
|
||||||
android:entries="@array/mode_entries"
|
android:entries="@array/mode_entries"
|
||||||
|
@ -228,6 +246,7 @@
|
||||||
android:key="pref_mode"
|
android:key="pref_mode"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/title_mode" />
|
android:title="@string/title_mode" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
|
@ -10,31 +10,31 @@ class HttpUtilTest {
|
||||||
fun testIdnToASCII() {
|
fun testIdnToASCII() {
|
||||||
// Regular URL remains unchanged
|
// Regular URL remains unchanged
|
||||||
val regularUrl = "https://example.com/path"
|
val regularUrl = "https://example.com/path"
|
||||||
assertEquals(regularUrl, HttpUtil.idnToASCII(regularUrl))
|
assertEquals(regularUrl, HttpUtil.toIdnUrl(regularUrl))
|
||||||
|
|
||||||
// Non-ASCII URL converts to ASCII (Punycode)
|
// Non-ASCII URL converts to ASCII (Punycode)
|
||||||
val nonAsciiUrl = "https://例子.测试/path"
|
val nonAsciiUrl = "https://例子.测试/path"
|
||||||
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
|
val expectedNonAscii = "https://xn--fsqu00a.xn--0zwm56d/path"
|
||||||
assertEquals(expectedNonAscii, HttpUtil.idnToASCII(nonAsciiUrl))
|
assertEquals(expectedNonAscii, HttpUtil.toIdnUrl(nonAsciiUrl))
|
||||||
|
|
||||||
// Mixed URL only converts the host part
|
// Mixed URL only converts the host part
|
||||||
val mixedUrl = "https://例子.com/测试"
|
val mixedUrl = "https://例子.com/测试"
|
||||||
val expectedMixed = "https://xn--fsqu00a.com/测试"
|
val expectedMixed = "https://xn--fsqu00a.com/测试"
|
||||||
assertEquals(expectedMixed, HttpUtil.idnToASCII(mixedUrl))
|
assertEquals(expectedMixed, HttpUtil.toIdnUrl(mixedUrl))
|
||||||
|
|
||||||
// URL with Basic Authentication using regular domain
|
// URL with Basic Authentication using regular domain
|
||||||
val basicAuthUrl = "https://user:password@example.com/path"
|
val basicAuthUrl = "https://user:password@example.com/path"
|
||||||
assertEquals(basicAuthUrl, HttpUtil.idnToASCII(basicAuthUrl))
|
assertEquals(basicAuthUrl, HttpUtil.toIdnUrl(basicAuthUrl))
|
||||||
|
|
||||||
// URL with Basic Authentication using non-ASCII domain
|
// URL with Basic Authentication using non-ASCII domain
|
||||||
val basicAuthNonAscii = "https://user:password@例子.测试/path"
|
val basicAuthNonAscii = "https://user:password@例子.测试/path"
|
||||||
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
|
val expectedBasicAuthNonAscii = "https://user:password@xn--fsqu00a.xn--0zwm56d/path"
|
||||||
assertEquals(expectedBasicAuthNonAscii, HttpUtil.idnToASCII(basicAuthNonAscii))
|
assertEquals(expectedBasicAuthNonAscii, HttpUtil.toIdnUrl(basicAuthNonAscii))
|
||||||
|
|
||||||
// URL with non-ASCII username and password
|
// URL with non-ASCII username and password
|
||||||
val nonAsciiAuth = "https://用户:密码@example.com/path"
|
val nonAsciiAuth = "https://用户:密码@example.com/path"
|
||||||
// Basic auth credentials should remain unchanged as they're percent-encoded separately
|
// Basic auth credentials should remain unchanged as they're percent-encoded separately
|
||||||
assertEquals(nonAsciiAuth, HttpUtil.idnToASCII(nonAsciiAuth))
|
assertEquals(nonAsciiAuth, HttpUtil.toIdnUrl(nonAsciiAuth))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.9.1"
|
agp = "8.10.1"
|
||||||
desugar_jdk_libs = "2.1.5"
|
desugarJdkLibs = "2.1.5"
|
||||||
gradleLicensePlugin = "0.9.8"
|
gradleLicensePlugin = "0.9.8"
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.21"
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.16.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.1"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
activity = "1.10.1"
|
activity = "1.10.1"
|
||||||
constraintlayout = "2.2.1"
|
constraintlayout = "2.2.1"
|
||||||
mmkvStatic = "1.3.12"
|
mmkvStatic = "1.3.12"
|
||||||
gson = "2.12.1"
|
gson = "2.12.1"
|
||||||
quickieFoss = "1.14.0"
|
quickieFoss = "1.14.0"
|
||||||
kotlinx-coroutines-android = "1.10.1"
|
kotlinxCoroutinesAndroid = "1.10.2"
|
||||||
kotlinx-coroutines-core = "1.10.1"
|
kotlinxCoroutinesCore = "1.10.2"
|
||||||
swiperefreshlayout = "1.1.0"
|
swiperefreshlayout = "1.1.0"
|
||||||
toasty = "1.5.2"
|
toasty = "1.5.2"
|
||||||
editorkit = "2.9.0"
|
editorkit = "2.9.0"
|
||||||
core = "3.5.3"
|
core = "3.5.3"
|
||||||
workRuntimeKtx = "2.10.0"
|
workRuntimeKtx = "2.10.2"
|
||||||
lifecycleViewmodelKtx = "2.8.7"
|
lifecycleViewmodelKtx = "2.9.1"
|
||||||
multidex = "2.0.1"
|
multidex = "2.0.1"
|
||||||
mockitoMockitoInline = "5.2.0"
|
mockitoMockitoInline = "5.2.0"
|
||||||
flexbox = "3.0.0"
|
flexbox = "3.0.0"
|
||||||
|
@ -30,7 +30,7 @@ recyclerview = "1.4.0"
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" }
|
||||||
gradle-license-plugin = { module = "com.jaredsburrows:gradle-license-plugin", version.ref = "gradleLicensePlugin" }
|
gradle-license-plugin = { module = "com.jaredsburrows:gradle-license-plugin", version.ref = "gradleLicensePlugin" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
|
@ -42,8 +42,8 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
|
||||||
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
|
mmkv-static = { module = "com.tencent:mmkv-static", version.ref = "mmkvStatic" }
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
|
quickie-foss = { module = "com.github.T8RIN.QuickieExtended:quickie-foss", version.ref = "quickieFoss" }
|
||||||
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version = "kotlinx-coroutines-android" }
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
|
||||||
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "kotlinx-coroutines-core" }
|
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
||||||
toasty = { module = "com.github.GrenderG:Toasty", version.ref = "toasty" }
|
toasty = { module = "com.github.GrenderG:Toasty", version.ref = "toasty" }
|
||||||
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
|
editorkit = { module = "com.blacksquircle.ui:editorkit", version.ref = "editorkit" }
|
||||||
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
|
language-base = { module = "com.blacksquircle.ui:language-base", version.ref = "editorkit" }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Thu Nov 14 12:42:51 BDT 2024
|
#Thu Nov 14 12:42:51 BDT 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
2
hysteria
2
hysteria
|
@ -1 +1 @@
|
||||||
Subproject commit 245c6e9bd17b1ef644f81fc4dafd0a1e1933da85
|
Subproject commit 2adeec2900a7a0e3689f118580174cc528f9995a
|
Loading…
Add table
Add a link
Reference in a new issue