Compare commits

...

16 Commits

Author SHA1 Message Date
2dust e9332168bf up 2.1.3 2026-04-26 11:23:14 +08:00
2dust 15fcfec4c8 Update AndroidLibXrayLite 2026-04-26 11:19:29 +08:00
hippo2025 a4baeeb0fa Add options to turn off local proxy completely or turn off UDP in SOCKS5 (#5548)
* Add options to turn off local proxy completely or turn off UDP in SOCKS5.

* Improve consistency of code style

* Auto-disable additional http proxy when local proxy is disabled

* Add local proxy summary and wire to prefs

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

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

---------

Co-authored-by: hippo2025 <4xcw60t6e@mozmail.com>
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-26 11:14:00 +08:00
2dust 093e51186d Upgrade Android Gradle Plugin and Gradle wrapper
Bump AGP to 9.2.0 in gradle/libs.versions.toml and update Gradle wrapper distribution to gradle-9.4.1 in gradle/wrapper/gradle-wrapper.properties. Keeps the build tooling up to date to take advantage of Gradle/AGP fixes and improvements.

Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-25 20:11:03 +08:00
2dust 14a29884e7 Disable VPN session name setting
Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-25 20:10:33 +08:00
solokot 30b0bd8d0d Update Russian translation (#5538)
* Update Russian translation

* Update Russian translation
2026-04-25 19:56:04 +08:00
hippo2025 ae84df7367 Add MTU and TTI settings in KCP network (#5539)
* Add MTU and TTI settings in KCP network

* Rename ProfileItem.tti to kcpTti

Rename the ProfileItem field `tti` to `kcpTti` and update all usages accordingly. Changes update parsing/serialization in FmtBase (map query param "tti" to config.kcpTti and emit it), KCP stream settings mapping in V2rayConfigManager, UI bindings in ServerActivity, and equality checks in ProfileItem to use kcpTti. This clarifies that the field applies to KCP-specific TTI values while preserving the external query key.

Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>

* Add kcpMtu/kcpTti for KCP transport

Introduce kcpMtu and kcpTti properties on ProfileItem and update equality checks. Parse and emit KCP-specific mtu/tti in FmtBase (use kcpMtu/kcpTti instead of the generic mtu). Wire kcpMtu into V2rayConfigManager when building KCP stream settings and update ServerActivity to read/write the UI field to profileItem.kcpMtu/kcpTti. This separates KCP MTU/TTI from the general mtu field and removes a duplicated kcpTti entry.

Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-25 19:55:44 +08:00
hippo2025 b673167731 Return null in IP test if http port is 0 (#5537)
new name

Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-25 19:13:23 +08:00
2dust 659f2ded09 up 2.1.2 2026-04-24 10:52:42 +08:00
2dust 585d64b0be Bump Android Gradle Plugin to 9.1.1
Update agp from 9.1.0 to 9.1.1 in V2rayNG/gradle/libs.versions.toml to pick up the latest patch fixes and keep the build tooling current.
2026-04-24 10:52:02 +08:00
fanymagnet 2ae2e747e2 Replace package names on UIDs in custom config (#5527)
* Replace package names on UIDs in custom config

* Update V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt

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

* Apply suggestions from code review

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

* Fix copilot review & detect tun optimization

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 10:39:19 +08:00
2dust 4199cee4e5 Use OkHttp with proxy auth and support SOCKS creds
Replace HttpURLConnection-based networking with OkHttp and add proxy username/password support. Introduces buildOkHttpClient, updated getUrlContent/getUrlContentWithUserAgent signatures, and downloadToFile; callers in AngConfigManager, SpeedtestManager, UpdateCheckerManager, PerAppProxyActivity, UserAssetActivity and UserAssetViewModel now pass SOCKS proxy credentials. Removes createProxyConnection and related URL-resolve logic, clears HTTP inbound auth/udp in V2rayConfigManager, and simplifies SettingsManager port generation. These changes enable authenticated proxy requests, improved redirect handling and more robust file downloads.
2026-04-23 20:40:17 +08:00
dependabot[bot] 8be8a912c4 Bump robinraju/release-downloader from 1.12 to 1.13 (#5520)
Bumps [robinraju/release-downloader](https://github.com/robinraju/release-downloader) from 1.12 to 1.13.
- [Release notes](https://github.com/robinraju/release-downloader/releases)
- [Commits](https://github.com/robinraju/release-downloader/compare/v1.12...v1.13)

---
updated-dependencies:
- dependency-name: robinraju/release-downloader
  dependency-version: '1.13'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 19:44:50 +08:00
Kapkap5454 851f22680a Add negative UID handling to process routing (#5509)
* Handle unidentified package UID resolution

Add special handling for unidentified package names.

* Add method to create special unidentified app item

Added a method to create a special unidentified app item for the app picker.

* Support unidentified app UID and label

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-04-19 19:48:59 +08:00
2dust 12abc99bfc Update hev-socks5-tunnel 2026-04-19 19:31:11 +08:00
2dust 07e50637a2 Update AndroidLibXrayLite 2026-04-19 19:30:40 +08:00
34 changed files with 449 additions and 239 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
popd
- name: Download libv2ray
uses: robinraju/release-downloader@v1.12
uses: robinraju/release-downloader@v1.13
with:
repository: '2dust/AndroidLibXrayLite'
tag: ${{ env.CURRENT_TAG }}
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 24
targetSdk = 36
versionCode = 721
versionName = "2.1.1"
versionCode = 723
versionName = "2.1.3"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
@@ -58,10 +58,12 @@ object AppConfig {
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
const val PREF_ENABLE_LOCAL_PROXY = "pref_enable_local_proxy"
const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_DYNAMIC_SOCKS_PORT = "pref_dynamic_socks_port"
const val PREF_SOCKS_USERNAME = "pref_socks_username"
const val PREF_SOCKS_PASSWORD = "pref_socks_password"
const val PREF_SOCKS_ENABLE_UDP = "pref_socks_enable_udp"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_DNS_HOSTS = "pref_dns_hosts"
@@ -219,6 +221,8 @@ object AppConfig {
const val REALITY = "reality"
const val HEADER_TYPE_HTTP = "http"
const val UNIDENTIFIED_PACKAGE = "__unknown_app__"
val DNS_ALIDNS_ADDRESSES = arrayListOf("223.5.5.5", "223.6.6.6", "2400:3200::1", "2400:3200:baba::1")
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")
@@ -29,6 +29,9 @@ data class ProfileItem(
var host: String? = null,
var path: String? = null,
var seed: String? = null,
var kcpMtu: Int? = null,
var kcpTti: Int? = null,
var quicSecurity: String? = null,
var quicKey: String? = null,
var mode: String? = null,
@@ -102,6 +105,8 @@ data class ProfileItem(
&& this.host == obj.host
&& this.path == obj.path
&& this.seed == obj.seed
&& this.kcpMtu == obj.kcpMtu
&& this.kcpTti == obj.kcpTti
&& this.quicSecurity == obj.quicSecurity
&& this.quicKey == obj.quicKey
&& this.mode == obj.mode
@@ -60,6 +60,8 @@ open class FmtBase {
config.path = queryParam["path"]
config.seed = queryParam["seed"]
config.kcpMtu = queryParam["mtu"]?.toIntOrNull()
config.kcpTti = queryParam["tti"]?.toIntOrNull()
config.quicSecurity = queryParam["quicSecurity"]
config.quicKey = queryParam["key"]
config.mode = queryParam["mode"]
@@ -112,6 +114,8 @@ open class FmtBase {
config.mldsa65Verify?.nullIfBlank()?.let { dicQuery["pqv"] = it }
config.flow?.nullIfBlank()?.let { dicQuery["flow"] = it }
config.finalMask?.nullIfBlank()?.let { dicQuery["fm"] = it }
config.kcpMtu?.let { dicQuery["mtu"] = it.toString() }
config.kcpTti?.let { dicQuery["tti"] = it.toString() }
// Add two keys for compatibility: "insecure" and "allowInsecure"
if (config.security == AppConfig.TLS) {
val insecureFlag = if (config.insecure == true) "1" else "0"
@@ -532,10 +532,12 @@ object AngConfigManager {
}
LogUtil.i(AppConfig.TAG, url)
val userAgent = it.subscription.userAgent
val proxyUsername = SettingsManager.getSocksUsername()
val proxyPassword = SettingsManager.getSocksPassword()
var configText = try {
val httpPort = SettingsManager.getHttpPort()
HttpUtil.getUrlContentWithUserAgent(url, userAgent, 15000, httpPort)
HttpUtil.getUrlContentWithUserAgent(url, userAgent, 15000, httpPort, proxyUsername, proxyPassword)
} catch (e: Exception) {
LogUtil.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
""
@@ -317,11 +317,7 @@ object SettingsManager {
}
private fun generateRandomSocksPort(): Int {
return if (Utils.isXray()) {
Random.nextInt(10000, 65536)
} else {
Random.nextInt(10000, 65535)
}
return Random.nextInt(10000, 65535)
}
/**
@@ -1,6 +1,5 @@
package com.v2ray.ang.handler
import android.content.Context
import android.os.SystemClock
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
@@ -83,49 +82,15 @@ object SpeedtestManager {
}
}
/**
* Tests the connection to a given URL and port.
*
* @param context The Context in which the test is running.
* @param port The port to connect to.
* @return A pair containing the elapsed time in milliseconds and the result message.
*/
fun testConnection(context: Context, port: Int): Pair<Long, String> {
var result: String
var elapsed = -1L
val conn = HttpUtil.createProxyConnection(SettingsManager.getDelayTestUrl(), port, 15000, 15000) ?: return Pair(elapsed, "")
try {
val start = SystemClock.elapsedRealtime()
val code = conn.responseCode
elapsed = SystemClock.elapsedRealtime() - start
result = when (code) {
204 -> context.getString(R.string.connection_test_available, elapsed)
200 if conn.contentLengthLong == 0L -> context.getString(R.string.connection_test_available, elapsed)
else -> throw IOException(
context.getString(R.string.connection_test_error_status_code, code)
)
}
} catch (e: IOException) {
LogUtil.e(AppConfig.TAG, "Connection test IOException", e)
result = context.getString(R.string.connection_test_error, e.message)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Connection test Exception", e)
result = context.getString(R.string.connection_test_error, e.message)
} finally {
conn.disconnect()
}
return Pair(elapsed, result)
}
fun getRemoteIPInfo(): String? {
val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL)
.takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL
val proxyUsername = SettingsManager.getSocksUsername()
val proxyPassword = SettingsManager.getSocksPassword()
val httpPort = SettingsManager.getHttpPort()
val content = HttpUtil.getUrlContent(url, 5000, httpPort) ?: return null
if (httpPort == 0) return null
val content = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword) ?: return null
val ipInfo = JsonUtil.fromJson(content, IPAPIInfo::class.java) ?: return null
val ip = listOf(
@@ -23,10 +23,13 @@ object UpdateCheckerManager {
AppConfig.APP_API_URL.concatUrl("latest")
}
val proxyUsername = SettingsManager.getSocksUsername()
val proxyPassword = SettingsManager.getSocksPassword()
var response = HttpUtil.getUrlContent(url, 5000)
if (response.isNullOrEmpty()) {
val httpPort = SettingsManager.getHttpPort()
response = HttpUtil.getUrlContent(url, 5000, httpPort)
response = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword)
?: throw IllegalStateException("Failed to get response")
}
@@ -61,39 +64,6 @@ object UpdateCheckerManager {
}
}
suspend fun downloadApk(context: Context, downloadUrl: String): File? = withContext(Dispatchers.IO) {
try {
val httpPort = SettingsManager.getHttpPort()
val connection = HttpUtil.createProxyConnection(downloadUrl, httpPort, 10000, 10000, true)
?: throw IllegalStateException("Failed to create connection")
try {
val apkFile = File(context.cacheDir, "update.apk")
LogUtil.i(AppConfig.TAG, "Downloading APK to: ${apkFile.absolutePath}")
FileOutputStream(apkFile).use { outputStream ->
connection.inputStream.use { inputStream ->
inputStream.copyTo(outputStream)
}
}
LogUtil.i(AppConfig.TAG, "APK download completed")
return@withContext apkFile
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to download APK: ${e.message}")
return@withContext null
} finally {
try {
connection.disconnect()
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Error closing connection: ${e.message}")
}
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to initiate download: ${e.message}")
return@withContext null
}
}
private fun compareVersions(version1: String, version2: String): Int {
val v1 = version1.split(".")
val v2 = version2.split(".")
@@ -98,36 +98,46 @@ object V2rayConfigManager {
return result
}
// check if tun inbound exists
val json = JsonUtil.parseString(raw) ?: return result
val inboundsJson = if (json.has("inbounds") && json.get("inbounds")?.isJsonNull == false) {
json.getAsJsonArray("inbounds")
} else {
JsonArray()
}
val json = JsonUtil.parseString(raw)?.takeIf { it.isJsonObject }?.asJsonObject ?: return result
for (i in 0 until inboundsJson.size()) {
val elem = inboundsJson.get(i)
if (elem.isJsonObject) {
val inb = elem.asJsonObject
val tag = if (inb.has("tag") && inb.get("tag")?.isJsonNull == false) inb.get("tag").asString else ""
if (tag == "tun") return result
// Check whether package names need to be replaced with UIDs
if (SettingsManager.canUseProcessRouting()) {
val rulesJson = json.get("routing")?.takeIf { it.isJsonObject }?.asJsonObject
?.get("rules")?.takeIf { it.isJsonArray }?.asJsonArray
?: JsonArray()
for (elem in rulesJson) {
val rule = elem.takeIf { it.isJsonObject }?.asJsonObject ?: continue
val process = rule.get("process")?.takeIf { it.isJsonArray }?.asJsonArray ?: continue
val packages = process.mapNotNull {
it.takeIf { it.isJsonPrimitive && it.asJsonPrimitive.isString }?.asString
}.takeIf { it.isNotEmpty() } ?: continue
val uids = PackageUidResolver.packageNamesToUids(context, packages).takeIf { it.isNotEmpty() } ?: continue
rule.add("process", JsonArray().apply { uids.forEach { add(it) } })
}
}
// add tun inbound from template
val templateConfig = initV2rayConfig(context) ?: return result
val inboundTun = templateConfig.inbounds.firstOrNull { it.tag == "tun" } ?: return result
inboundTun.settings?.mtu = SettingsManager.getVpnMtu()
// add to json
inboundsJson.add(JsonUtil.parseString(JsonUtil.toJson(inboundTun)))
if (inboundsJson.size() == 1) {
json.add("inbounds", inboundsJson)
// check if tun inbound exists
val inboundsJson = json.get("inbounds")?.takeIf { it.isJsonArray }?.asJsonArray
?: JsonArray().also { json.add("inbounds", it) }
val tunNotExists = inboundsJson.none { elem ->
elem.isJsonObject && elem.asJsonObject.get("protocol")
?.takeIf { it.isJsonPrimitive && it.asJsonPrimitive.isString }
?.asString == "tun"
}
val updatedRaw = JsonUtil.toJsonPretty(json) ?: return result
return ConfigResult(true, guid, updatedRaw)
if (tunNotExists) {
// add tun inbound from template
initV2rayConfig(context)?.let { templateConfig ->
templateConfig.inbounds.firstOrNull { it.tag == "tun" }?.let { inboundTun ->
inboundTun.settings?.mtu = SettingsManager.getVpnMtu()
inboundsJson.add(JsonUtil.parseString(JsonUtil.toJson(inboundTun)))
}
}
}
return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result
}
/**
@@ -374,6 +384,12 @@ object V2rayConfigManager {
*/
private fun getInbounds(v2rayConfig: V2rayConfig): Boolean {
try {
val vpn = SettingsManager.isVpnMode()
val useHev = SettingsManager.isUsingHevTun()
val forcedByHev = vpn && useHev
val enableLocalProxy = forcedByHev || MmkvManager.decodeSettingsBool(AppConfig.PREF_ENABLE_LOCAL_PROXY, true)
val socksPort = SettingsManager.getSocksPort()
val socksUsername = SettingsManager.getSocksUsername()
val socksPassword = SettingsManager.getSocksPassword()
@@ -386,6 +402,7 @@ object V2rayConfigManager {
inbound1.listen = AppConfig.LOOPBACK
}
inbound1.port = socksPort
inbound1.settings?.udp = MmkvManager.decodeSettingsBool(AppConfig.PREF_SOCKS_ENABLE_UDP, true)
if (socksUsername != null && socksPassword != null) {
inbound1.settings?.auth = "password"
inbound1.settings?.accounts = listOf(
@@ -416,9 +433,15 @@ object V2rayConfigManager {
inbound2.tag = EConfigType.HTTP.name.lowercase()
inbound2.port = SettingsManager.getHttpPort()
inbound2.protocol = EConfigType.HTTP.name.lowercase()
inbound2.settings?.auth = null
inbound2.settings?.udp = null
v2rayConfig.inbounds.add(inbound2)
}
if (!enableLocalProxy) {
v2rayConfig.inbounds.removeIf { it.protocol == "socks" || it.protocol == "http" }
}
if (needTun()) {
val inboundTun = v2rayConfig.inbounds.firstOrNull { e -> e.tag == "tun" }
inboundTun?.settings?.mtu = SettingsManager.getVpnMtu()
@@ -1293,7 +1316,10 @@ object V2rayConfigManager {
}
NetworkType.KCP.type -> {
streamSettings.kcpSettings = StreamSettingsBean.KcpSettingsBean()
val kcpSetting = StreamSettingsBean.KcpSettingsBean()
profileItem.kcpMtu?.let { kcpSetting.mtu = it }
profileItem.kcpTti?.let { kcpSetting.tti = it }
streamSettings.kcpSettings = kcpSetting
val udpMaskList = mutableListOf<StreamSettingsBean.FinalMaskBean.MaskBean>()
if (!headerType.isNullOrEmpty() && headerType != "none") {
val kcpHeaderType = when {
@@ -253,7 +253,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
builder.setSession(V2RayServiceManager.getRunningServerName())
//builder.setSession(V2RayServiceManager.getRunningServerName())
}
/**
@@ -1,5 +1,6 @@
package com.v2ray.ang.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -7,11 +8,13 @@ import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAppPickerBinding
import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.PackageUidResolver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -101,6 +104,21 @@ class AppPickerActivity : BaseActivity() {
addCustomDividerToRecyclerView(binding.recyclerView, this, R.drawable.custom_divider)
}
@SuppressLint("UseCompatLoadingForDrawables")
private fun createSpecialItemUnidentified(): AppInfo {
val icon = requireNotNull(
getDrawable(android.R.drawable.ic_menu_help)
?: getDrawable(android.R.drawable.sym_def_app_icon)
) { "No fallback drawable available" }
return AppInfo(
appName = getString(R.string.app_picker_unknown_app),
packageName = AppConfig.UNIDENTIFIED_PACKAGE,
appIcon = icon,
isSystemApp = false,
isSelected = 0
)
}
private fun loadApps() {
showLoading()
@@ -108,7 +126,8 @@ class AppPickerActivity : BaseActivity() {
try {
val apps = withContext(Dispatchers.IO) {
val appsList = AppManagerUtil.loadNetworkAppList(this@AppPickerActivity)
sortApps(appsList)
val sortedApps = sortApps(appsList)
listOf(createSpecialItemUnidentified()) + sortedApps
}
appsAll = apps
@@ -191,8 +191,10 @@ class PerAppProxyActivity : BaseActivity() {
lifecycleScope.launch(Dispatchers.IO) {
var content = HttpUtil.getUrlContent(url, 5000)
if (content.isNullOrEmpty()) {
val proxyUsername = SettingsManager.getSocksUsername()
val proxyPassword = SettingsManager.getSocksPassword()
val httpPort = SettingsManager.getHttpPort()
content = HttpUtil.getUrlContent(url, 5000, httpPort) ?: ""
content = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword) ?: ""
}
launch(Dispatchers.Main) {
//LogUtil.i(AppConfig.TAG, content)
@@ -132,6 +132,9 @@ class ServerActivity : BaseActivity() {
private val et_port_hop_interval: EditText? by lazy { findViewById(R.id.et_port_hop_interval) }
private val et_bandwidth_down: EditText? by lazy { findViewById(R.id.et_bandwidth_down) }
private val et_bandwidth_up: EditText? by lazy { findViewById(R.id.et_bandwidth_up) }
private val et_kcp_mtu: EditText? by lazy { findViewById(R.id.et_kcp_mtu) }
private val et_kcp_tti: EditText? by lazy { findViewById(R.id.et_kcp_tti) }
private val layout_kcp: LinearLayout? by lazy { findViewById(R.id.layout_kcp) }
private val et_extra: EditText? by lazy { findViewById(R.id.et_extra) }
private val et_fm: EditText? by lazy { findViewById(R.id.et_fm) }
private val layout_extra: LinearLayout? by lazy { findViewById(R.id.layout_extra) }
@@ -245,6 +248,14 @@ class ServerActivity : BaseActivity() {
)
et_fm?.text = Utils.getEditable(config?.finalMask)
layout_kcp?.visibility =
when (networks[position]) {
NetworkType.KCP.type -> View.VISIBLE
else -> View.GONE
}
et_kcp_mtu?.text = Utils.getEditable(config?.kcpMtu?.toString().orEmpty())
et_kcp_tti?.text = Utils.getEditable(config?.kcpTti?.toString().orEmpty())
layout_extra?.visibility =
when (networks[position]) {
NetworkType.XHTTP.type -> View.VISIBLE
@@ -569,6 +580,8 @@ class ServerActivity : BaseActivity() {
profileItem.xhttpMode = transportTypes(networks[network])[type]
profileItem.xhttpExtra = et_extra?.text?.toString()?.trim().nullIfBlank()
profileItem.finalMask = et_fm?.text?.toString()?.trim()?.nullIfBlank()
profileItem.kcpMtu = et_kcp_mtu?.text?.toString()?.toIntOrNull()
profileItem.kcpTti = et_kcp_tti?.text?.toString()?.toIntOrNull()
}
private fun saveTls(config: ProfileItem) {
@@ -51,13 +51,19 @@ class SettingsActivity : BaseActivity() {
private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val dynamicSocksPort by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_DYNAMIC_SOCKS_PORT) }
private val hevTunLogLevel by lazy { findPreference<ListPreference>(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) }
private val hevTunRwTimeout by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT) }
private val useHevTun by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_USE_HEV_TUNNEL) }
private val enableLocalProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_ENABLE_LOCAL_PROXY) }
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val dynamicSocksPort by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_DYNAMIC_SOCKS_PORT) }
private val socksUsername by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_USERNAME) }
private val socksPassword by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PASSWORD) }
private val socksEnableUdp by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_SOCKS_ENABLE_UDP) }
private val proxySharing by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PROXY_SHARING) }
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
// Use MMKV as the storage backend for all Preferences
// This prevents inconsistencies between SharedPreferences and MMKV
@@ -115,6 +121,11 @@ class SettingsActivity : BaseActivity() {
true
}
enableLocalProxy?.setOnPreferenceChangeListener { _, newValue ->
updateEnableLocalProxy(newValue as Boolean)
true
}
dynamicSocksPort?.setOnPreferenceChangeListener { _, newValue ->
updateDynamicSocksPort(newValue as Boolean)
true
@@ -174,6 +185,9 @@ class SettingsActivity : BaseActivity() {
// Initialize mode-dependent UI states
updateMode(MmkvManager.decodeSettingsString(AppConfig.PREF_MODE, VPN))
// Initialize local proxy state
updateEnableLocalProxy(MmkvManager.decodeSettingsBool(AppConfig.PREF_ENABLE_LOCAL_PROXY, true))
// Initialize mux-dependent UI states
updateMux(MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false))
@@ -276,12 +290,44 @@ class SettingsActivity : BaseActivity() {
}
private fun updateDynamicSocksPort(enabled: Boolean) {
socksPort?.isEnabled = !enabled
socksPort?.isEnabled = (enableLocalProxy?.isChecked == true) && !enabled
}
private fun updateEnableLocalProxy(enabled: Boolean) {
val dynamic = MmkvManager.decodeSettingsBool(AppConfig.PREF_DYNAMIC_SOCKS_PORT, false)
socksPort?.isEnabled = enabled && !dynamic
dynamicSocksPort?.isEnabled = enabled
socksUsername?.isEnabled = enabled
socksPassword?.isEnabled = enabled
socksEnableUdp?.isEnabled = enabled
proxySharing?.isEnabled = enabled
if (!enabled) {
if (appendHttpProxy?.isChecked == true) {
appendHttpProxy?.isChecked = false
MmkvManager.encodeSettings(AppConfig.PREF_APPEND_HTTP_PROXY, false)
}
appendHttpProxy?.isEnabled = false
} else {
val vpn = MmkvManager.decodeSettingsString(AppConfig.PREF_MODE) == VPN
appendHttpProxy?.isEnabled = vpn
}
}
private fun updateHevTunSettings(enabled: Boolean) {
hevTunLogLevel?.isEnabled = enabled
hevTunRwTimeout?.isEnabled = enabled
if (enabled) {
if (enableLocalProxy?.isChecked == false) {
enableLocalProxy?.isChecked = true
MmkvManager.encodeSettings(AppConfig.PREF_ENABLE_LOCAL_PROXY, true)
}
enableLocalProxy?.isEnabled = false
} else {
enableLocalProxy?.isEnabled = true
}
updateEnableLocalProxy(enableLocalProxy?.isChecked == true)
}
}
@@ -173,9 +173,11 @@ class UserAssetActivity : HelperBaseActivity() {
showLoading()
toast(R.string.msg_downloading_content)
val proxyUsername = SettingsManager.getSocksUsername()
val proxyPassword = SettingsManager.getSocksPassword()
val httpPort = SettingsManager.getHttpPort()
lifecycleScope.launch(Dispatchers.IO) {
val result = viewModel.downloadGeoFiles(extDir, httpPort)
val result = viewModel.downloadGeoFiles(extDir, httpPort, proxyUsername, proxyPassword)
withContext(Dispatchers.Main) {
if (result.successCount > 0) {
toast(getString(R.string.title_update_config_count, result.successCount))
@@ -3,10 +3,8 @@ package com.v2ray.ang.util
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.util.Utils.encode
import com.v2ray.ang.util.Utils.urlDecode
import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
import java.net.IDN
import java.net.Inet6Address
import java.net.InetAddress
@@ -15,6 +13,10 @@ import java.net.MalformedURLException
import java.net.Proxy
import java.net.URI
import java.net.URL
import java.util.concurrent.TimeUnit
import okhttp3.Credentials
import okhttp3.OkHttpClient
import okhttp3.Request
object HttpUtil {
@@ -106,13 +108,31 @@ object HttpUtil {
* @param httpPort The HTTP port to use.
* @return The content of the URL as a string.
*/
fun getUrlContent(url: String, timeout: Int, httpPort: Int = 0): String? {
val conn = createProxyConnection(url, httpPort, timeout, timeout) ?: return null
fun getUrlContent(
url: String,
timeout: Int,
httpPort: Int = 0,
proxyUsername: String? = null,
proxyPassword: String? = null
): String? {
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = true)
val requestBuilder = Request.Builder()
.url(url)
.get()
.header("Connection", "close")
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
}
try {
return conn.inputStream.bufferedReader().readText()
} catch (_: Exception) {
} finally {
conn.disconnect()
client.newCall(requestBuilder.build()).execute().use { response ->
if (!response.isSuccessful) {
LogUtil.w(AppConfig.TAG, "Failed to get URL content, code=${response.code}")
return null
}
return response.body?.string()
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to get URL content", e)
}
return null
}
@@ -127,127 +147,147 @@ object HttpUtil {
* @throws IOException If an I/O error occurs.
*/
@Throws(IOException::class)
fun getUrlContentWithUserAgent(url: String?, userAgent: String?, timeout: Int = 15000, httpPort: Int = 0): String {
fun getUrlContentWithUserAgent(
url: String?,
userAgent: String?,
timeout: Int = 15000,
httpPort: Int = 0,
proxyUsername: String? = null,
proxyPassword: String? = null
): String {
var currentUrl = url
var redirects = 0
val maxRedirects = 3
while (redirects++ < maxRedirects) {
if (currentUrl == null) continue
val conn = createProxyConnection(currentUrl, httpPort, timeout, timeout) ?: continue
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = false)
val finalUserAgent = if (userAgent.isNullOrBlank()) {
"v2rayNG/${BuildConfig.VERSION_NAME}"
} else {
userAgent
}
conn.setRequestProperty("User-agent", finalUserAgent)
conn.connect()
val requestBuilder = Request.Builder()
.url(currentUrl)
.get()
.header("User-agent", finalUserAgent)
.header("Connection", "close")
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
}
val responseCode = conn.responseCode
when (responseCode) {
in 300..399 -> {
val location = resolveLocation(conn)
conn.disconnect()
if (location.isNullOrEmpty()) {
throw IOException("Redirect location not found")
client.newCall(requestBuilder.build()).execute().use { response ->
when {
response.isRedirect -> {
val location = response.header("Location")
if (location.isNullOrEmpty()) {
throw IOException("Redirect location not found")
}
currentUrl = resolveLocation(currentUrl, location)
if (currentUrl.isNullOrEmpty()) {
throw IOException("Failed to resolve redirect location")
}
continue
}
currentUrl = location
continue
}
else -> try {
return conn.inputStream.use { it.bufferedReader().readText() }
} finally {
conn.disconnect()
response.isSuccessful -> {
return response.body?.string() ?: ""
}
else -> {
throw IOException("Request failed with status code ${response.code}")
}
}
}
}
throw IOException("Too many redirects")
}
/**
* Creates an HttpURLConnection object connected through a proxy.
*
* @param urlStr The target URL address.
* @param port The port of the proxy server.
* @param connectTimeout The connection timeout in milliseconds (default is 15000 ms).
* @param readTimeout The read timeout in milliseconds (default is 15000 ms).
* @param needStream Whether the connection needs to support streaming.
* @return Returns a configured HttpURLConnection object, or null if it fails.
*/
fun createProxyConnection(
urlStr: String,
port: Int,
connectTimeout: Int = 15000,
readTimeout: Int = 15000,
needStream: Boolean = false
): HttpURLConnection? {
private fun buildOkHttpClient(
timeout: Int,
httpPort: Int,
proxyUsername: String?,
proxyPassword: String?,
followRedirects: Boolean
): OkHttpClient {
val builder = OkHttpClient.Builder()
.connectTimeout(timeout.toLong(), TimeUnit.MILLISECONDS)
.readTimeout(timeout.toLong(), TimeUnit.MILLISECONDS)
.followRedirects(followRedirects)
.followSslRedirects(followRedirects)
var conn: HttpURLConnection? = null
try {
val url = URL(urlStr)
// Create a connection
conn = if (port == 0) {
url.openConnection()
} else {
url.openConnection(
Proxy(
Proxy.Type.HTTP,
InetSocketAddress(LOOPBACK, port)
)
)
} as HttpURLConnection
// Set connection and read timeouts
conn.connectTimeout = connectTimeout
conn.readTimeout = readTimeout
if (!needStream) {
// Set request headers
conn.setRequestProperty("Connection", "close")
// Disable automatic redirects
conn.instanceFollowRedirects = false
// Disable caching
conn.useCaches = false
if (httpPort != 0) {
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(LOOPBACK, httpPort)))
if (!proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
builder.proxyAuthenticator { _, response ->
if (response.request.header("Proxy-Authorization") != null) {
null
} else {
response.request.newBuilder()
.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
.build()
}
}
}
//Add Basic Authorization
url.userInfo?.let {
conn.setRequestProperty(
"Authorization",
"Basic ${encode(urlDecode(it))}"
)
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to create proxy connection", e)
// If an exception occurs, close the connection and return null
conn?.disconnect()
return null
}
return conn
return builder.build()
}
// Returns absolute URL string location header sets
fun resolveLocation(conn: HttpURLConnection): String? {
val raw = conn.getHeaderField("Location")?.trim()?.takeIf { it.isNotEmpty() } ?: return null
// Try check url is relative or absolute
private fun resolveLocation(baseUrl: String, raw: String): String? {
return try {
val locUri = URI(raw)
val baseUri = conn.url.toURI()
val baseUri = URI(baseUrl)
val resolved = if (locUri.isAbsolute) locUri else baseUri.resolve(locUri)
resolved.toURL().toString()
} catch (_: Exception) {
// Fallback: url resolver, also should handles //host/...
try {
URL(raw).toString() // absolute with protocol
URL(raw).toString()
} catch (_: MalformedURLException) {
try {
URL(conn.url, raw).toString()
URL(URL(baseUrl), raw).toString()
} catch (_: MalformedURLException) {
null
}
}
}
}
fun downloadToFile(
url: String,
targetFile: File,
timeout: Int = 15000,
httpPort: Int = 0,
proxyUsername: String? = null,
proxyPassword: String? = null
): Boolean {
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = true)
val requestBuilder = Request.Builder()
.url(url)
.get()
.header("Connection", "close")
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
}
return try {
client.newCall(requestBuilder.build()).execute().use { response ->
if (!response.isSuccessful) {
LogUtil.w(AppConfig.TAG, "Failed to download file, code=${response.code}, url=$url")
return false
}
val body = response.body ?: return false
body.byteStream().use { input ->
targetFile.outputStream().use { output ->
input.copyTo(output)
}
}
true
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to download file: $url", e)
false
}
}
}
@@ -2,12 +2,11 @@ package com.v2ray.ang.util
import android.content.Context
import android.content.pm.PackageManager
import com.v2ray.ang.AppConfig
import java.util.concurrent.ConcurrentHashMap
object PackageUidResolver {
private const val TAG = "PackageUidResolver"
// In-process cache to avoid resolving the same package UID repeatedly.
private val packageUidCache = ConcurrentHashMap<String, String>()
@@ -32,13 +31,20 @@ object PackageUidResolver {
}
private fun resolveUid(context: Context, packageName: String): String? {
// Special token for connections whose UID cannot be resolved (mapped to -1)
if (packageName == AppConfig.UNIDENTIFIED_PACKAGE) {
val uid = "-1"
LogUtil.d(AppConfig.TAG, "Special package: $packageName -> UID: $uid")
return uid
}
return try {
val uid = context.packageManager.getPackageUid(packageName, 0).toString()
LogUtil.d(TAG, "Package: $packageName -> UID: $uid")
LogUtil.d(AppConfig.TAG, "Package: $packageName -> UID: $uid")
uid
} catch (_: PackageManager.NameNotFoundException) {
LogUtil.w(TAG, "Package not found: $packageName")
LogUtil.w(AppConfig.TAG, "Package not found: $packageName")
null
}
}
}
}
@@ -10,8 +10,6 @@ import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
class UserAssetViewModel : ViewModel() {
private val assets = mutableListOf<AssetUrlCache>()
@@ -61,7 +59,12 @@ class UserAssetViewModel : ViewModel() {
}
}
fun downloadGeoFiles(extDir: File, httpPort: Int): GeoDownloadResult {
fun downloadGeoFiles(
extDir: File,
httpPort: Int,
proxyUsername: String? = null,
proxyPassword: String? = null
): GeoDownloadResult {
val snapshot = getAssets()
var successCount = 0
val failures = mutableListOf<String>()
@@ -69,7 +72,7 @@ class UserAssetViewModel : ViewModel() {
snapshot.forEach { cache ->
val item = cache.assetUrl
val portsToTry = if (httpPort == 0) listOf(0) else listOf(httpPort, 0)
if (portsToTry.any { tryDownload(item, extDir, it) }) {
if (portsToTry.any { tryDownload(item, extDir, it, proxyUsername, proxyPassword) }) {
successCount++
} else {
failures.add(item.remarks)
@@ -79,25 +82,22 @@ class UserAssetViewModel : ViewModel() {
return GeoDownloadResult(successCount, failures.size, failures)
}
private fun tryDownload(item: AssetUrlItem, extDir: File, httpPort: Int): Boolean {
private fun tryDownload(
item: AssetUrlItem,
extDir: File,
httpPort: Int,
proxyUsername: String? = null,
proxyPassword: String? = null
): Boolean {
val targetTemp = File(extDir, item.remarks + "_temp")
val target = File(extDir, item.remarks)
val conn = HttpUtil.createProxyConnection(item.url, httpPort, 15000, 15000, needStream = true) ?: return false
try {
val responseCode = conn.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
conn.inputStream.use { inputStream ->
FileOutputStream(targetTemp).use { output ->
inputStream.copyTo(output)
}
}
if (HttpUtil.downloadToFile(item.url, targetTemp, 15000, httpPort, proxyUsername, proxyPassword)) {
targetTemp.renameTo(target)
return true
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to download geo file: ${item.remarks}", e)
} finally {
conn.disconnect()
}
return false
}
@@ -96,6 +96,49 @@
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_kcp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_kcp_mtu" />
<EditText
android:id="@+id/et_kcp_mtu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_kcp_tti" />
<EditText
android:id="@+id/et_kcp_tti"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_extra"
android:layout_width="match_parent"
@@ -89,6 +89,8 @@
<string name="server_lab_reserved">محجوز (اختياري)</string>
<string name="server_lab_local_address">العنوان المحلي (اختياري IPv4/IPv6، مفصولة بفواصل)</string>
<string name="server_lab_local_mtu">Mtu (اختياري، الافتراضي 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (اختياري، الافتراضي في النواة 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (اختياري، الافتراضي في النواة 50)</string>
<string name="toast_success">نجاح</string>
<string name="toast_failure">فشل</string>
<string name="toast_none_data">لا يوجد شيء</string>
@@ -221,6 +223,10 @@
<string name="summary_pref_allow_insecure">عند TLS، الافتراضي هو السماح غير الآمن</string>
<string name="title_pref_socks_port">منفذ بروكسي Local</string>
<string name="title_pref_enable_local_proxy">تفعيل الوكيل المحلي</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">مصادقة socks5 ليست آمنة تمامًا عند استخدام UDP</string>
<string name="summary_pref_socks_port">منفذ بروكسي Local</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">সংরক্ষিত (ঐচ্ছিক)</string>
<string name="server_lab_local_address">স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা)</string>
<string name="server_lab_local_mtu">MTU (ঐচ্ছিক, ডিফল্ট 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (ঐচ্ছিক, কোর ডিফল্ট 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (ঐচ্ছিক, কোর ডিফল্ট 50)</string>
<string name="toast_success">সফল</string>
<string name="toast_failure">ব্যর্থ</string>
<string name="toast_none_data">কোনও তথ্য নেই</string>
@@ -221,6 +223,10 @@
<string name="summary_pref_allow_insecure">যখন TLS, ডিফল্টভাবে allowInsecure</string>
<string name="title_pref_socks_port">Local প্রক্সি পোর্ট</string>
<string name="title_pref_enable_local_proxy">লোকাল প্রক্সি সক্ষম করুন</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">UDP ব্যবহার করার সময় socks5 প্রমাণীকরণ সম্পূর্ণরূপে নিরাপদ নয়</string>
<string name="summary_pref_socks_port">Local প্রক্সি পোর্ট</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_mtu">Mtu(اختیاری، پؽش فرز 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (اختیاری، پؽش فرز هسته 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (اختیاری، پؽش فرز هسته 50)</string>
<string name="toast_success">وا مووفقیت ٱنجوم وابی</string>
<string name="toast_failure">شکست خرد</string>
<string name="toast_none_data">هیچ داده ای وۊجۊڌ نڌاره</string>
@@ -221,6 +223,10 @@
<string name="summary_pref_allow_insecure">مجال و کار بوردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن فعال هڌ.</string>
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
<string name="title_pref_enable_local_proxy">فعال‌سازی پروکسی محلی</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">احراز هویت socks5 هنگام استفاده از UDP کاملاً ایمن نیست</string>
<string name="summary_pref_socks_port">پورت پروکسی مهلی</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
<string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string>
<string name="server_lab_local_mtu">MTU (اختیاری، پیش‌فرض 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (اختیاری، پیش‌فرض هسته 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (اختیاری، پیش‌فرض هسته 50)</string>
<string name="toast_success">با موفقیت انجام شد</string>
<string name="toast_failure">شکست</string>
<string name="toast_none_data">هیچ داده ای وجود ندارد</string>
@@ -219,6 +221,10 @@
<string name="summary_pref_allow_insecure">هنگام استفاده از TLS، به طور پیش‌ فرض مجوز ناامن فعال است.</string>
<string name="title_pref_socks_port">پورت پروکسی محلی</string>
<string name="title_pref_enable_local_proxy">فعال‌سازی پروکسی محلی</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">احراز هویت socks5 هنگام استفاده از UDP کاملاً ایمن نیست</string>
<string name="summary_pref_socks_port">پورت پروکسی محلی</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
+10 -3
View File
@@ -2,7 +2,7 @@
<resources>
<string name="app_widget_name">v2rayNG</string>
<string name="app_tile_name">v2rayNG</string>
<string name="app_tile_first_use">Первое использование этой функции, пожалуйста, используйте приложение, чтобы добавить сервер</string>
<string name="app_tile_first_use">Первое использование этой функции, пожалуйста, используйте приложение, чтобы добавить профиль</string>
<string name="navigation_drawer_open">Открыть панель навигации</string>
<string name="navigation_drawer_close">Закрыть панель навигации</string>
<string name="migration_success">Успешный перенос данных!</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved (необязательно, через запятую)</string>
<string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string>
<string name="server_lab_local_mtu">MTU (необязательно, по умолчанию 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (необязательно, по умолчанию 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (необязательно, по умолчанию 50)</string>
<string name="toast_success">Успешно</string>
<string name="toast_failure">Ошибка</string>
<string name="toast_none_data">Ничего нет</string>
@@ -143,6 +145,7 @@
<string name="menu_item_import_proxy_app">Импорт из буфера обмена</string>
<string name="per_app_proxy_settings">Выбор приложений</string>
<string name="per_app_proxy_settings_enable">Использовать выбор приложений</string>
<string name="app_picker_unknown_app">Неизвестное приложение (неопознанный UID)</string>
<!-- Preferences -->
<string name="title_settings">Настройки</string>
@@ -222,6 +225,10 @@
<string name="summary_pref_allow_insecure">Для TLS по умолчанию разрешены небезопасные соединения</string>
<string name="title_pref_socks_port">Порт локального прокси</string>
<string name="title_pref_enable_local_proxy">Включить локальный прокси</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">Аутентификация socks5 не полностью безопасна при использовании UDP</string>
<string name="summary_pref_socks_port">Порт локального прокси</string>
<string name="title_pref_dynamic_socks_port">Динамически менять порт локального прокси</string>
<string name="summary_pref_dynamic_socks_port">Использовать случайный порт локального прокси при каждом создании соединения</string>
@@ -321,7 +328,7 @@
<!-- RoutingSettingActivity -->
<string name="routing_settings_domain_strategy">Доменная стратегия</string>
<string name="routing_settings_title">Маршрутизация</string>
<string name="routing_settings_tips">Через запятую (,)\nТолько что-то одно: домен, IP или процесс</string>
<string name="routing_settings_tips">Через запятую (,)\nЧто-то одно: домен, IP или процесс</string>
<string name="routing_settings_save">Сохранить</string>
<string name="routing_settings_delete">Очистить</string>
<string name="routing_settings_rule_title">Настройка правил маршрутизации</string>
@@ -334,7 +341,7 @@
<string name="routing_settings_locked">Постоянное (сохранится при импорте правил)</string>
<string name="routing_settings_domain">Домен</string>
<string name="routing_settings_ip">IP</string>
<string name="routing_settings_process">Процесс (название пакета поддерживается только при использовании Xray TUN, включённой функции routeOnly и ОС Android 10+)</string>
<string name="routing_settings_process">Процесс (название пакета; поддерживается только при использовании Xray TUN, включённой функции routeOnly и ОС Android 10+)</string>
<string name="routing_settings_port">Порт</string>
<string name="routing_settings_protocol">Протокол</string>
<string name="routing_settings_protocol_tip">[http,tls,bittorrent]</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved (Không bắt buộc)</string>
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string>
<string name="server_lab_local_mtu">MTU (Không bắt buộc, mặc định là 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (Không bắt buộc, mặc định core là 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (Không bắt buộc, mặc định core là 50)</string>
<string name="toast_success">Thành công!</string>
<string name="toast_failure">Đã xảy ra lỗi, vui lòng thử lại!</string>
<string name="toast_none_data">Không có gì ở đây!</string>
@@ -221,6 +223,10 @@
<string name="summary_pref_allow_insecure">Khi nhập những cấu hình có bảo mật TLS, mặc định sẽ không xác minh chứng chỉ.</string>
<string name="title_pref_socks_port">Cổng Proxy Local</string>
<string name="title_pref_enable_local_proxy">Bật proxy cục bộ</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">Xác thực socks5 không hoàn toàn an toàn khi sử dụng UDP</string>
<string name="summary_pref_socks_port">Cổng Proxy Local</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved (可选,逗号隔开)</string>
<string name="server_lab_local_address">本地地址 (可选 IPv4/IPv6,逗号隔开)</string>
<string name="server_lab_local_mtu">Mtu (可选, 默认 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (可选, 默认 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (可选, 默认 50)</string>
<string name="toast_success">成功</string>
<string name="toast_failure">失败</string>
<string name="toast_none_data">没有数据</string>
@@ -218,6 +220,10 @@
<string name="summary_pref_allow_insecure">传输层安全选 tls 时,默认跳过证书验证 (allowInsecure)</string>
<string name="title_pref_socks_port">本地代理端口</string>
<string name="title_pref_enable_local_proxy">启用本地代理</string>
<string name="summary_pref_enable_local_proxy">不开启时没有 socks 代理,无法通过代理更新订阅等</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">使用 UDP 时,socks5 认证并不完全安全</string>
<string name="summary_pref_socks_port">本地代理端口</string>
<string name="title_pref_dynamic_socks_port">启用动态本地代理端口</string>
<string name="summary_pref_dynamic_socks_port">每次生成 inbound 时随机生成一个本地代理端口</string>
@@ -88,6 +88,8 @@
<string name="server_lab_reserved">Reserved (可選,逗號隔開)</string>
<string name="server_lab_local_address">本機位址 (可選 IPv4/IPv6,逗號隔開)</string>
<string name="server_lab_local_mtu">MTU (可選, 預設 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (可選, 預設 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (可選, 預設 50)</string>
<string name="toast_success">成功</string>
<string name="toast_failure">失敗</string>
<string name="toast_none_data">無資料</string>
@@ -219,6 +221,10 @@
<string name="summary_pref_allow_insecure">傳輸層安全選 tls 時,預設跳過憑證驗證 (allowInsecure)</string>
<string name="title_pref_socks_port">本地代理埠</string>
<string name="title_pref_enable_local_proxy">啟用本地代理</string>
<string name="summary_pref_enable_local_proxy">未開啟時沒有 socks 代理,無法透過代理更新訂閱等</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">使用 UDP 時,socks5 認證並不完全安全</string>
<string name="summary_pref_socks_port">本地代理埠</string>
<string name="title_pref_dynamic_socks_port">啟用動態本地代理埠</string>
<string name="summary_pref_dynamic_socks_port">每次產生 inbound 時隨機產生一個本地代理埠</string>
@@ -89,6 +89,8 @@
<string name="server_lab_reserved">Reserved(Optional, separated by commas)</string>
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
<string name="server_lab_local_mtu">Mtu(optional, default 1420)</string>
<string name="server_lab_kcp_mtu">mKCP MTU (Optional, core default 1350)</string>
<string name="server_lab_kcp_tti">mKCP TTI (Optional, core default 50)</string>
<string name="toast_success">Success</string>
<string name="toast_failure">Failure</string>
<string name="toast_none_data">There is nothing</string>
@@ -144,6 +146,7 @@
<string name="menu_item_import_proxy_app">Import from Clipboard</string>
<string name="per_app_proxy_settings">Per-app settings</string>
<string name="per_app_proxy_settings_enable">Enable per-app</string>
<string name="app_picker_unknown_app">Unknown app (unidentified UID)</string>
<!-- Preferences -->
<string name="title_settings">Settings</string>
@@ -224,6 +227,10 @@
<string name="summary_pref_allow_insecure">When TLS is selected, allow insecure connections by default</string>
<string name="title_pref_socks_port">Local proxy port</string>
<string name="title_pref_enable_local_proxy">Enable local proxy</string>
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
<string name="summary_pref_socks_enable_udp">SOCKS5 authentication is not fully secure when using UDP.</string>
<string name="summary_pref_socks_port">Local proxy port</string>
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
+17 -6
View File
@@ -146,15 +146,20 @@
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_proxy_sharing_enabled"
android:summary="@string/summary_pref_proxy_sharing_enabled"
android:title="@string/title_pref_proxy_sharing_enabled" />
android:key="pref_allow_insecure"
android:title="@string/title_pref_allow_insecure" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_enable_local_proxy"
android:summary="@string/summary_pref_enable_local_proxy"
android:title="@string/title_pref_enable_local_proxy" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_allow_insecure"
android:summary="@string/summary_pref_allow_insecure"
android:title="@string/title_pref_allow_insecure" />
android:key="pref_proxy_sharing_enabled"
android:summary="@string/summary_pref_proxy_sharing_enabled"
android:title="@string/title_pref_proxy_sharing_enabled" />
<EditTextPreference
android:inputType="number"
@@ -179,6 +184,12 @@
android:summary="@string/summary_pref_socks_password"
android:title="@string/title_pref_socks_password" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_socks_enable_udp"
android:summary="@string/summary_pref_socks_enable_udp"
android:title="@string/title_pref_socks_enable_udp" />
<EditTextPreference
android:key="pref_remote_dns"
android:summary="@string/summary_pref_remote_dns"
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
agp = "9.1.0"
agp = "9.2.0"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.3.10"
+1 -1
View File
@@ -1,6 +1,6 @@
#Thu Nov 14 12:42:51 BDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists