Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9332168bf | |||
| 15fcfec4c8 | |||
| a4baeeb0fa | |||
| 093e51186d | |||
| 14a29884e7 | |||
| 30b0bd8d0d | |||
| ae84df7367 | |||
| b673167731 | |||
| 659f2ded09 | |||
| 585d64b0be | |||
| 2ae2e747e2 | |||
| 4199cee4e5 | |||
| 8be8a912c4 | |||
| 851f22680a | |||
| 12abc99bfc | |||
| 07e50637a2 |
@@ -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 }}
|
||||
|
||||
+1
-1
Submodule AndroidLibXrayLite updated: 03a47ef527...d8bfc35e93
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
+1
-1
Submodule hev-socks5-tunnel updated: cf312ec222...dfabaa9cad
Reference in New Issue
Block a user