Compare commits

...

23 Commits

Author SHA1 Message Date
2dust
92f33ee3f8 up 1.10.13 2025-08-04 20:21:17 +08:00
2dust
084346b348 Update AndroidLibXrayLite 2025-08-04 19:46:45 +08:00
2dust
15de18b736 Add Enable New TUN Feature option
When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks
2025-08-04 18:32:16 +08:00
Peyman
b60423d1c0 Update Persian translate (#4791) 2025-08-04 10:00:28 +08:00
2dust
86e38c6963 up 1.10.12 2025-08-03 17:37:32 +08:00
2dust
9ba4d7e691 Add Mldsa65Verify 2025-08-03 14:13:06 +08:00
2dust
16e72787c9 Optimize V2RayVpnService 2025-08-02 17:39:12 +08:00
2dust
3ffac8b29f Standardized file naming 2025-08-02 17:06:06 +08:00
2dust
10df1b44ea Optimize V2RayVpnService add Tun2SocksManager 2025-08-02 16:20:41 +08:00
2dust
fa12878258 Update libs.versions.toml 2025-08-02 16:08:28 +08:00
2dust
0f1ea1e119 Update hysteria 2025-08-02 16:08:15 +08:00
2dust
1959608f24 Update AndroidLibXrayLite 2025-08-02 16:08:04 +08:00
2dust
0e041a6e9a up 1.10.11 2025-07-26 20:21:01 +08:00
2dust
c78ef380cc up 1.10.10 2025-07-24 19:34:31 +08:00
2dust
57362a4bde Update AndroidLibXrayLite 2025-07-24 19:28:19 +08:00
DHR60
7f24ad534f Fix Intelligent Selection not working (#4767) 2025-07-24 19:22:15 +08:00
2dust
680832614b up 1.10.9 2025-07-10 20:20:53 +08:00
2dust
4357abbff4 Bug fix
https://github.com/2dust/v2rayNG/issues/4723
2025-07-10 20:11:34 +08:00
solokot
905be66c3f Update Russian translation (#4725) 2025-07-09 19:44:45 +08:00
Hossein Abaspanah
318a7b54a5 Update Luri Bakhtiari translation (#4724) 2025-07-09 19:44:34 +08:00
DHR60
5db2df77a0 feat. Intelligent Selection (#4716)
rename

Adds intelligent selection method setting

Adds KDoc
2025-07-07 20:17:09 +08:00
DHR60
d039cb9edf Fix export count (#4713) 2025-07-06 11:16:32 +08:00
solokot
9a1654bae9 Update Russian translation (#4701) 2025-07-01 15:23:05 +08:00
51 changed files with 936 additions and 247 deletions

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 35
versionCode = 658
versionName = "1.10.8"
versionCode = 663
versionName = "1.10.13"
multiDexEnabled = true
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -57,10 +57,12 @@ object AppConfig {
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method"
const val PREF_INTELLIGENT_SELECTION_METHOD = "pref_intelligent_selection_method"
const val PREF_MODE = "pref_mode"
const val PREF_IS_BOOTED = "pref_is_booted"
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
const val PREF_GEO_FILES_SOURCES = "pref_geo_files_sources"
const val PREF_USE_HEV_TUNNEL = "pref_use_hev_tunnel"
/** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
@@ -162,6 +164,7 @@ object AppConfig {
/** Give a good name to this, IDK*/
const val VPN = "VPN"
const val VPN_MTU = 1500
// Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"

View File

@@ -44,6 +44,7 @@ data class ProfileItem(
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = null,
var mldsa65Verify: String? = null,
var secretKey: String? = null,
var preSharedKey: String? = null,

View File

@@ -11,6 +11,7 @@ data class SubscriptionItem(
var prevProfile: String? = null,
var nextProfile: String? = null,
var filter: String? = null,
var intelligentSelectionFilter: String? = null,
var allowInsecureUrl: Boolean = false,
)

View File

@@ -264,7 +264,8 @@ data class V2rayConfig(
val show: Boolean = false,
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = null
var spiderX: String? = null,
var mldsa65Verify: String? = null
)
data class QuicSettingBean(
@@ -496,14 +497,14 @@ data class V2rayConfig(
var domainStrategy: String,
var domainMatcher: String? = null,
var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null
var balancers: List<BalancerBean>? = null
) {
data class RulesBean(
var type: String = "field",
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
var outboundTag: String? = null,
var balancerTag: String? = null,
var port: String? = null,
val sourcePort: String? = null,
@@ -515,6 +516,32 @@ data class V2rayConfig(
val attrs: String? = null,
val domainMatcher: String? = null
)
data class BalancerBean(
val tag: String,
val selector: List<String>,
val fallbackTag: String? = null,
val strategy: StrategyObject? = null
)
data class StrategyObject(
val type: String = "random", // "random" | "roundRobin" | "leastPing" | "leastLoad"
val settings: StrategySettingsObject? = null
)
data class StrategySettingsObject(
val expected: Int? = null,
val maxRTT: String? = null,
val tolerance: Double? = null,
val baselines: List<String>? = null,
val costs: List<CostObject>? = null
)
data class CostObject(
val regexp: Boolean = false,
val match: String,
val value: Double
)
}
data class PolicyBean(
@@ -532,6 +559,26 @@ data class V2rayConfig(
)
}
data class ObservatoryObject(
val subjectSelector: List<String>,
val probeUrl: String,
val probeInterval: String,
val enableConcurrency: Boolean = false
)
data class BurstObservatoryObject(
val subjectSelector: List<String>,
val pingConfig: PingConfigObject
) {
data class PingConfigObject(
val destination: String,
val connectivity: String? = null,
val interval: String,
val sampling: Int,
val timeout: String? = null
)
}
data class FakednsBean(
var ipPool: String = "198.18.0.0/15",
var poolSize: Int = 10000

View File

@@ -83,6 +83,7 @@ open class FmtBase {
config.publicKey = queryParam["pbk"]
config.shortId = queryParam["sid"]
config.spiderX = queryParam["spx"]
config.mldsa65Verify = queryParam["pqv"]
config.flow = queryParam["flow"]
}
@@ -101,6 +102,7 @@ open class FmtBase {
config.publicKey.let { if (it.isNotNullEmpty()) dicQuery["pbk"] = it.orEmpty() }
config.shortId.let { if (it.isNotNullEmpty()) dicQuery["sid"] = it.orEmpty() }
config.spiderX.let { if (it.isNotNullEmpty()) dicQuery["spx"] = it.orEmpty() }
config.mldsa65Verify.let { if (it.isNotNullEmpty()) dicQuery["pqv"] = it.orEmpty() }
config.flow.let { if (it.isNotNullEmpty()) dicQuery["flow"] = it.orEmpty() }
val networkType = NetworkType.fromString(config.network)

View File

@@ -71,7 +71,7 @@ object AngConfigManager {
if (sb.count() > 0) {
Utils.setClipboard(context, sb.toString())
}
return sb.lines().count()
return sb.lines().count() - 1
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to share non-custom configs to clipboard", e)
return -1
@@ -490,4 +490,31 @@ object AngConfigManager {
MmkvManager.encodeSubscription("", subItem)
return 1
}
/**
* Creates an intelligent selection configuration based on multiple server configurations.
*
* @param context The application context used for configuration generation.
* @param guidList The list of server GUIDs to be included in the intelligent selection.
* Each GUID represents a server configuration that will be combined.
* @param subid The subscription ID to associate with the generated configuration.
* This helps organize the configuration under a specific subscription.
* @return The GUID key of the newly created intelligent selection configuration,
* or null if the operation fails (e.g., empty guidList or configuration parsing error).
*/
fun createIntelligentSelection(
context: Context,
guidList: List<String>,
subid: String
): String? {
if (guidList.isEmpty()) {
return null
}
val result = V2rayConfigManager.genV2rayConfig(context, guidList) ?: return null
val config = CustomFmt.parse(JsonUtil.toJson(result)) ?: return null
config.subscriptionId = subid
val key = MmkvManager.encodeServerConfig("", config)
MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(result) ?: "")
return key
}
}

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.app.Notification
import android.app.NotificationChannel
@@ -12,12 +12,10 @@ import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.ui.MainActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -27,7 +25,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.math.min
object NotificationService {
object NotificationManager {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
@@ -50,7 +48,7 @@ object NotificationService {
lastQueryTime = System.currentTimeMillis()
var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
outboundTags?.remove(AppConfig.TAG_DIRECT)
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
@@ -66,15 +64,15 @@ object NotificationService {
proxyTotal += up + down
}
}
val directUplink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = V2RayServiceManager.queryStats(TAG_DIRECT, AppConfig.DOWNLINK)
val directUplink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK)
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds
)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
@@ -102,12 +100,12 @@ object NotificationService {
val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = ANG_PACKAGE
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
restartV2RayIntent.`package` = ANG_PACKAGE
restartV2RayIntent.`package` = AppConfig.ANG_PACKAGE
restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.util
package com.v2ray.ang.handler
import android.content.Context
import android.os.SystemClock
@@ -7,11 +7,12 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.fmt.Hysteria2Fmt
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.service.ProcessService
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
import java.io.File
object PluginUtil {
object PluginServiceManager {
private const val HYSTERIA2 = "libhysteria2.so"
private val procService: ProcessService by lazy {

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.annotation.SuppressLint
import android.app.NotificationChannel
@@ -11,11 +11,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
import com.v2ray.ang.R
import com.v2ray.ang.handler.AngConfigManager.updateConfigViaSub
import com.v2ray.ang.handler.MmkvManager
object SubscriptionUpdater {
@@ -24,7 +20,7 @@ object SubscriptionUpdater {
private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification =
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL)
NotificationCompat.Builder(applicationContext, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
.setWhen(0)
.setTicker("Update")
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
@@ -46,18 +42,18 @@ object SubscriptionUpdater {
val subItem = sub.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
notification.setChannelId(AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
val channel =
NotificationChannel(
SUBSCRIPTION_UPDATE_CHANNEL,
SUBSCRIPTION_UPDATE_CHANNEL_NAME,
AppConfig.SUBSCRIPTION_UPDATE_CHANNEL,
AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_MIN
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(3, notification.build())
Log.i(AppConfig.TAG, "subscription automatic update: ---${subItem.remarks}")
updateConfigViaSub(Pair(sub.first, subItem))
AngConfigManager.updateConfigViaSub(Pair(sub.first, subItem))
notification.setContentText("Updating ${subItem.remarks}")
}
notificationManager.cancel(3)

View File

@@ -1,4 +1,4 @@
package com.v2ray.ang.service
package com.v2ray.ang.handler
import android.app.Service
import android.content.BroadcastReceiver
@@ -13,12 +13,11 @@ import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.service.ServiceControl
import com.v2ray.ang.service.V2RayProxyOnlyService
import com.v2ray.ang.service.V2RayVpnService
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.handler.PluginServiceManager
import com.v2ray.ang.util.Utils
import go.Seq
import kotlinx.coroutines.CoroutineScope
@@ -163,16 +162,16 @@ object V2RayServiceManager {
if (coreController.isRunning == false) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
return false
}
try {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
NotificationService.showNotification(currentConfig)
NotificationService.startSpeedNotification(currentConfig)
NotificationManager.showNotification(currentConfig)
NotificationManager.startSpeedNotification(currentConfig)
PluginUtil.runPlugin(service, config, result.socksPort)
PluginServiceManager.runPlugin(service, config, result.socksPort)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to startup service", e)
return false
@@ -199,14 +198,14 @@ object V2RayServiceManager {
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
}
PluginUtil.stopPlugin()
PluginServiceManager.stopPlugin()
return true
}
@@ -364,12 +363,12 @@ object V2RayServiceManager {
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats")
NotificationService.stopSpeedNotification(currentConfig)
NotificationManager.stopSpeedNotification(currentConfig)
}
Intent.ACTION_SCREEN_ON -> {
Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats")
NotificationService.startSpeedNotification(currentConfig)
NotificationManager.startSpeedNotification(currentConfig)
}
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.NetworkType
@@ -15,6 +16,7 @@ import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.StreamSettingsBean
import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.fmt.CustomFmt
import com.v2ray.ang.fmt.HttpFmt
import com.v2ray.ang.fmt.ShadowsocksFmt
import com.v2ray.ang.fmt.SocksFmt
@@ -52,6 +54,27 @@ object V2rayConfigManager {
}
}
/**
* Generates a V2ray configuration from multiple server profiles.
*
* @param context The context of the caller.
* @param guidList A list of server GUIDs to be included in the generated configuration.
* Each GUID represents a unique server profile stored in the system.
* @return A V2rayConfig object containing the combined configuration of all specified servers,
* or null if the operation fails (e.g., no valid configurations found, parsing errors)
*/
fun genV2rayConfig(context: Context, guidList: List<String>): V2rayConfig? {
try {
val configList = guidList.mapNotNull { guid ->
MmkvManager.decodeServerConfig(guid)
}
return genV2rayMultipleConfig(context, configList)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to generate V2ray config", e)
return null
}
}
/**
* Retrieves the speedtest V2ray configuration for the given GUID.
*
@@ -142,6 +165,80 @@ object V2rayConfigManager {
return result
}
private fun genV2rayMultipleConfig(context: Context, configList: List<ProfileItem>): V2rayConfig? {
val validConfigs = configList.asSequence().filter { it.server.isNotNullEmpty() }
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
.filter { it.configType != EConfigType.CUSTOM }
.filter { it.configType != EConfigType.HYSTERIA2 }
.filter { config ->
if (config.subscriptionId.isEmpty()) {
return@filter true
}
val subItem = MmkvManager.decodeSubscription(config.subscriptionId)
if (subItem?.intelligentSelectionFilter.isNullOrEmpty() || config.remarks.isEmpty()) {
return@filter true
}
Regex(pattern = subItem?.intelligentSelectionFilter!!).containsMatchIn(input = config.remarks)
}.toList()
if (validConfigs.isEmpty()) {
Log.w(AppConfig.TAG, "All configs are invalid")
return null
}
val v2rayConfig = initV2rayConfig(context) ?: return null
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
val subIds = configList.map { it.subscriptionId }.toHashSet()
val remarks = if (subIds.size == 1 && subIds.first().isNotEmpty()) {
val sub = MmkvManager.decodeSubscription(subIds.first())
(sub?.remarks ?: "") + context.getString(R.string.intelligent_selection)
} else {
context.getString(R.string.intelligent_selection)
}
v2rayConfig.remarks = remarks
getInbounds(v2rayConfig)
v2rayConfig.outbounds.removeAt(0)
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
var index = 0
for (config in validConfigs) {
index++
val outbound = convertProfile2Outbound(config) ?: continue
val ret = updateOutboundWithGlobalSettings(outbound)
if (!ret) continue
outbound.tag = "proxy-$index"
outboundsList.add(outbound)
}
outboundsList.addAll(v2rayConfig.outbounds)
v2rayConfig.outbounds = ArrayList(outboundsList)
getRouting(v2rayConfig)
getFakeDns(v2rayConfig)
getDns(v2rayConfig)
getBalance(v2rayConfig)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
getCustomLocalDns(v2rayConfig)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
v2rayConfig.policy = null
}
//Resolve and add to DNS Hosts
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
resolveOutboundDomainsToHosts(v2rayConfig)
}
return v2rayConfig
}
/**
* Retrieves the normal V2ray configuration for speedtest.
*
@@ -741,6 +838,78 @@ object V2rayConfigManager {
return true
}
/**
* Configures load balancing settings for the V2ray configuration.
*
* @param v2rayConfig The V2ray configuration object to be modified with balancing settings
*/
private fun getBalance(v2rayConfig: V2rayConfig)
{
try {
v2rayConfig.routing.rules.forEach { rule ->
if (rule.outboundTag == "proxy") {
rule.outboundTag = null
rule.balancerTag = "proxy-round"
}
}
if (MmkvManager.decodeSettingsString(AppConfig.PREF_INTELLIGENT_SELECTION_METHOD, "0") == "0") {
val balancer = V2rayConfig.RoutingBean.BalancerBean(
tag = "proxy-round",
selector = listOf("proxy-"),
strategy = V2rayConfig.RoutingBean.StrategyObject(
type = "leastPing"
)
)
v2rayConfig.routing.balancers = listOf(balancer)
v2rayConfig.observatory = V2rayConfig.ObservatoryObject(
subjectSelector = listOf("proxy-"),
probeUrl = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DELAY_TEST_URL,
probeInterval = "3m",
enableConcurrency = true
)
} else {
val balancer = V2rayConfig.RoutingBean.BalancerBean(
tag = "proxy-round",
selector = listOf("proxy-"),
strategy = V2rayConfig.RoutingBean.StrategyObject(
type = "leastLoad"
)
)
v2rayConfig.routing.balancers = listOf(balancer)
v2rayConfig.burstObservatory = V2rayConfig.BurstObservatoryObject(
subjectSelector = listOf("proxy-"),
pingConfig = V2rayConfig.BurstObservatoryObject.PingConfigObject(
destination = MmkvManager.decodeSettingsString(AppConfig.PREF_DELAY_TEST_URL) ?: AppConfig.DELAY_TEST_URL,
interval = "5m",
sampling = 2,
timeout = "30s"
)
)
}
if (v2rayConfig.routing.domainStrategy == "IPIfNonMatch") {
v2rayConfig.routing.rules.add(
RulesBean(
ip = arrayListOf("0.0.0.0/0", "::/0"),
balancerTag = "proxy-round",
type = "field"
)
)
} else {
v2rayConfig.routing.rules.add(
RulesBean(
network = "tcp,udp",
balancerTag = "proxy-round",
type = "field"
)
)
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to configure balance", e)
}
}
/**
* Updates the outbound with fragment settings for traffic optimization.
*
@@ -1066,6 +1235,7 @@ object V2rayConfigManager {
val publicKey = profileItem.publicKey
val shortId = profileItem.shortId
val spiderX = profileItem.spiderX
val mldsa65Verify = profileItem.mldsa65Verify
streamSettings.security = if (streamSecurity.isEmpty()) null else streamSecurity
if (streamSettings.security == null) return
@@ -1077,6 +1247,7 @@ object V2rayConfigManager {
publicKey = if (publicKey.isNullOrEmpty()) null else publicKey,
shortId = if (shortId.isNullOrEmpty()) null else shortId,
spiderX = if (spiderX.isNullOrEmpty()) null else spiderX,
mldsa65Verify = if (mldsa65Verify.isNullOrEmpty()) null else mldsa65Verify,
)
if (streamSettings.security == AppConfig.TLS) {
streamSettings.tlsSettings = tlsSetting

View File

@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class BootReceiver : BroadcastReceiver() {
/**

View File

@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class TaskerReceiver : BroadcastReceiver() {

View File

@@ -10,7 +10,7 @@ import android.os.Build
import android.widget.RemoteViews
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class WidgetProvider : AppWidgetProvider() {
/**

View File

@@ -13,6 +13,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference

View File

@@ -0,0 +1,96 @@
package com.v2ray.ang.service
import android.content.Context
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import java.io.File
/**
* Manages the tun2socks process that handles VPN traffic
*/
class TProxyService(
private val context: Context,
private val vpnInterface: ParcelFileDescriptor,
private val isRunningProvider: () -> Boolean,
private val restartCallback: () -> Unit
) : Tun2SocksControl {
companion object {
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyStartService(configPath: String, fd: Int)
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyStopService()
@JvmStatic
@Suppress("FunctionName")
private external fun TProxyGetStats(): LongArray?
init {
System.loadLibrary("hev-socks5-tunnel")
}
}
/**
* Starts the tun2socks process with the appropriate parameters.
*/
override fun startTun2Socks() {
Log.i(AppConfig.TAG, "Starting HevSocks5Tunnel via JNI")
val configContent = buildConfig()
val configFile = File(context.filesDir, "hev-socks5-tunnel.yaml").apply {
writeText(configContent)
}
Log.i(AppConfig.TAG, "Config file created: ${configFile.absolutePath}")
Log.d(AppConfig.TAG, "Config content:\n$configContent")
try {
Log.i(AppConfig.TAG, "TProxyStartService...")
TProxyStartService(configFile.absolutePath, vpnInterface.fd)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "HevSocks5Tunnel exception: ${e.message}")
}
}
private fun buildConfig(): String {
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
return buildString {
appendLine("tunnel:")
appendLine(" mtu: $VPN_MTU")
appendLine(" ipv4: ${vpnConfig.ipv4Client}")
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
appendLine(" ipv6: '${vpnConfig.ipv6Client}'")
}
appendLine("socks5:")
appendLine(" port: ${socksPort}")
appendLine(" address: ${AppConfig.LOOPBACK}")
appendLine(" udp: 'udp'")
MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL)?.let { logPref ->
if (logPref != "none") {
val logLevel = if (logPref == "warning") "warn" else logPref
appendLine("misc:")
appendLine(" log-level: $logLevel")
}
}
}
}
/**
* Stops the tun2socks process
*/
override fun stopTun2Socks() {
try {
Log.i(AppConfig.TAG, "TProxyStopService...")
TProxyStopService()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to stop hev-socks5-tunnel", e)
}
}
}

View File

@@ -0,0 +1,19 @@
package com.v2ray.ang.service
/**
* Interface that defines the control operations for tun2socks implementations.
*
* This interface is implemented by different tunnel solutions like:
*/
interface Tun2SocksControl {
/**
* Starts the tun2socks process with the appropriate parameters.
* This initializes the VPN tunnel and connects it to the SOCKS proxy.
*/
fun startTun2Socks()
/**
* Stops the tun2socks process and cleans up resources.
*/
fun stopTun2Socks()
}

View File

@@ -0,0 +1,129 @@
package com.v2ray.ang.service
import android.content.Context
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.os.ParcelFileDescriptor
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
/**
* Manages the tun2socks process that handles VPN traffic
*/
class Tun2SocksService(
private val context: Context,
private val vpnInterface: ParcelFileDescriptor,
private val isRunningProvider: () -> Boolean,
private val restartCallback: () -> Unit
) : Tun2SocksControl {
companion object {
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var process: Process
/**
* Starts the tun2socks process with the appropriate parameters.
*/
override fun startTun2Socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val cmd = arrayListOf(
File(context.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", vpnConfig.ipv4Router,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "${AppConfig.LOOPBACK}:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",
"--enable-udprelay",
"--loglevel", "notice"
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(vpnConfig.ipv6Router)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(
MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT),
AppConfig.PORT_LOCAL_DNS.toInt()
)
cmd.add("--dnsgw")
cmd.add("${AppConfig.LOOPBACK}:${localDnsPort}")
}
Log.i(AppConfig.TAG, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(context.filesDir)
.start()
Thread {
Log.i(AppConfig.TAG, "$TUN2SOCKS check")
process.waitFor()
Log.i(AppConfig.TAG, "$TUN2SOCKS exited")
if (isRunningProvider()) {
Log.i(AppConfig.TAG, "$TUN2SOCKS restart")
restartCallback()
}
}.start()
Log.i(AppConfig.TAG, "$TUN2SOCKS process info: $process")
sendFd()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e)
}
}
/**
* Sends the file descriptor to the tun2socks process.
* Attempts to send the file descriptor multiple times if necessary.
*/
private fun sendFd() {
val fd = vpnInterface.fileDescriptor
val path = File(context.filesDir, "sock_path").absolutePath
Log.i(AppConfig.TAG, "LocalSocket path: $path")
CoroutineScope(Dispatchers.IO).launch {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
localSocket.outputStream.write(42)
}
break
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e)
if (tries > 5) break
tries += 1
}
}
}
/**
* Stops the tun2socks process
*/
override fun stopTun2Socks() {
try {
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy")
if (::process.isInitialized) {
process.destroy()
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
}
}

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper
import java.lang.ref.SoftReference

View File

@@ -9,10 +9,10 @@ import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.PluginServiceManager
import com.v2ray.ang.handler.SpeedtestManager
import com.v2ray.ang.handler.V2rayConfigManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.PluginUtil
import com.v2ray.ang.util.Utils
import go.Seq
import kotlinx.coroutines.CoroutineScope
@@ -78,7 +78,7 @@ class V2RayTestService : Service() {
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
if (config.configType == EConfigType.HYSTERIA2) {
val delay = PluginUtil.realPingHy2(this, config)
val delay = PluginServiceManager.realPingHy2(this, config)
return delay
} else {
val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(this, guid)

View File

@@ -5,8 +5,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
@@ -19,26 +17,20 @@ import android.util.Log
import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.NotificationManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl {
companion object {
private const val VPN_MTU = 1500
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var mInterface: ParcelFileDescriptor
private var isRunning = false
private lateinit var process: Process
private var tun2SocksService: Tun2SocksControl? = null
/**destroy
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
@@ -95,7 +87,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
override fun onDestroy() {
super.onDestroy()
NotificationService.cancelNotification()
NotificationManager.cancelNotification()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -111,7 +103,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
override fun startService() {
setup()
setupService()
}
override fun stopService() {
@@ -134,13 +126,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Sets up the VPN service.
* Prepares the VPN and configures it if preparation is successful.
*/
private fun setup() {
private fun setupService() {
val prepare = prepare(this)
if (prepare != null) {
return
}
if (setupVpnService() != true) {
if (configureVpnService() != true) {
return
}
@@ -151,17 +143,52 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Configures the VPN service.
* @return True if the VPN service was configured successfully, false otherwise.
*/
private fun setupVpnService(): Boolean {
// If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters.
private fun configureVpnService(): Boolean {
val builder = Builder()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
// Configure network settings (addresses, routing and DNS)
configureNetworkSettings(builder)
// Configure app-specific settings (session name and per-app proxy)
configurePerAppProxy(builder)
// Close the old interface since the parameters have been changed
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
// Configure platform-specific features
configurePlatformFeatures(builder)
// Create a new interface using the builder and save the parameters
try {
mInterface = builder.establish()!!
isRunning = true
return true
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e)
stopV2Ray()
}
return false
}
/**
* Configures the basic network settings for the VPN.
* This includes IP addresses, routing rules, and DNS servers.
*
* @param builder The VPN Builder to configure
*/
private fun configureNetworkSettings(builder: Builder) {
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val bypassLan = SettingsManager.routingRulesetsBypassLan()
// Configure IPv4 settings
builder.setMtu(VPN_MTU)
builder.addAddress(vpnConfig.ipv4Client, 30)
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan()
// Configure routing rules
if (bypassLan) {
AppConfig.ROUTED_IP_LIST.forEach {
val addr = it.split('/')
@@ -171,56 +198,37 @@ class V2RayVpnService : VpnService(), ServiceControl {
builder.addRoute("0.0.0.0", 0)
}
// Configure IPv6 if enabled
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(vpnConfig.ipv6Client, 126)
if (bypassLan) {
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
builder.addRoute("fc00::", 18) //Xray-core default FakeIPv6 Pool
builder.addRoute("2000::", 3) // Currently only 1/8 of total IPv6 is in use
builder.addRoute("fc00::", 18) // Xray-core default FakeIPv6 Pool
} else {
builder.addRoute("::", 0)
}
}
// if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
// } else {
SettingsManager.getVpnDnsServers()
.forEach {
// Configure DNS servers
//if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
// builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
//} else {
SettingsManager.getVpnDnsServers().forEach {
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
}
// }
builder.setSession(V2RayServiceManager.getRunningServerName())
val selfPackageName = BuildConfig.APPLICATION_ID
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)) {
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
//process self package
if (bypassApps) apps?.add(selfPackageName) else apps?.remove(selfPackageName)
apps?.forEach {
try {
if (bypassApps)
builder.addDisallowedApplication(it)
else
builder.addAllowedApplication(it)
} catch (e: PackageManager.NameNotFoundException) {
Log.e(AppConfig.TAG, "Failed to configure app in VPN: ${e.localizedMessage}", e)
}
}
} else {
builder.addDisallowedApplication(selfPackageName)
}
// Close the old interface since the parameters have been changed.
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
/**
* Configures platform-specific VPN features for different Android versions.
*
* @param builder The VPN Builder to configure
*/
private fun configurePlatformFeatures(builder: Builder) {
// Android P (API 28) and above: Configure network callbacks
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
@@ -229,24 +237,58 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
// Android Q (API 29) and above: Configure metering and HTTP proxy
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_APPEND_HTTP_PROXY)) {
builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOOPBACK, SettingsManager.getHttpPort()))
}
}
// Create a new interface using the builder and save the parameters.
try {
mInterface = builder.establish()!!
isRunning = true
return true
} catch (e: Exception) {
// non-nullable lateinit var
Log.e(AppConfig.TAG, "Failed to establish VPN interface", e)
stopV2Ray()
}
return false
/**
* Configures per-app proxy rules for the VPN builder.
*
* - If per-app proxy is not enabled, disallow the VPN service's own package.
* - If no apps are selected, disallow the VPN service's own package.
* - If bypass mode is enabled, disallow all selected apps (including self).
* - If proxy mode is enabled, only allow the selected apps (excluding self).
*
* @param builder The VPN Builder to configure.
*/
private fun configurePerAppProxy(builder: Builder) {
val selfPackageName = BuildConfig.APPLICATION_ID
// If per-app proxy is not enabled, disallow the VPN service's own package and return
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY) == false) {
builder.addDisallowedApplication(selfPackageName)
return
}
// If no apps are selected, disallow the VPN service's own package and return
val apps = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
if (apps.isNullOrEmpty()) {
builder.addDisallowedApplication(selfPackageName)
return
}
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
// Handle the VPN service's own package according to the mode
if (bypassApps) apps.add(selfPackageName) else apps.remove(selfPackageName)
apps.forEach {
try {
if (bypassApps) {
// In bypass mode, disallow the selected apps
builder.addDisallowedApplication(it)
} else {
// In proxy mode, only allow the selected apps
builder.addAllowedApplication(it)
}
} catch (e: PackageManager.NameNotFoundException) {
Log.e(AppConfig.TAG, "Failed to configure app in VPN: ${e.localizedMessage}", e)
}
}
}
/**
@@ -254,80 +296,23 @@ class V2RayVpnService : VpnService(), ServiceControl {
* Starts the tun2socks process with the appropriate parameters.
*/
private fun runTun2socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS")
val socksPort = SettingsManager.getSocksPort()
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig()
val cmd = arrayListOf(
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", vpnConfig.ipv4Router,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "$LOOPBACK:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
"--enable-udprelay",
"--loglevel", "notice"
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL) == true) {
tun2SocksService = TProxyService(
context = applicationContext,
vpnInterface = mInterface,
isRunningProvider = { isRunning },
restartCallback = { runTun2socks() }
)
} else {
tun2SocksService = Tun2SocksService(
context = applicationContext,
vpnInterface = mInterface,
isRunningProvider = { isRunning },
restartCallback = { runTun2socks() }
)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) {
cmd.add("--netif-ip6addr")
cmd.add(vpnConfig.ipv6Router)
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw")
cmd.add("$LOOPBACK:${localDnsPort}")
}
Log.i(AppConfig.TAG, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(applicationContext.filesDir)
.start()
Thread {
Log.i(AppConfig.TAG, "$TUN2SOCKS check")
process.waitFor()
Log.i(AppConfig.TAG, "$TUN2SOCKS exited")
if (isRunning) {
Log.i(AppConfig.TAG, "$TUN2SOCKS restart")
runTun2socks()
}
}.start()
Log.i(AppConfig.TAG, "$TUN2SOCKS process info : ${process.toString()}")
sendFd()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e)
}
}
/**
* Sends the file descriptor to the tun2socks process.
* Attempts to send the file descriptor multiple times if necessary.
*/
private fun sendFd() {
val fd = mInterface.fileDescriptor
val path = File(applicationContext.filesDir, "sock_path").absolutePath
Log.i(AppConfig.TAG, "LocalSocket path : $path")
CoroutineScope(Dispatchers.IO).launch {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
localSocket.outputStream.write(42)
}
break
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e)
if (tries > 5) break
tries += 1
}
}
tun2SocksService?.startTun2Socks()
}
/**
@@ -348,12 +333,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
try {
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy")
process.destroy()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
tun2SocksService?.stopTun2Socks()
tun2SocksService = null
V2RayServiceManager.stopCoreLoop()
@@ -373,3 +354,4 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
}
}

View File

@@ -38,7 +38,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MigrateManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers
@@ -385,6 +385,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
R.id.intelligent_selection_all -> {
mainViewModel.createIntelligentSelectionAll()
true
}
R.id.service_restart -> {
restartV2Ray()
true

View File

@@ -26,7 +26,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View File

@@ -2,7 +2,7 @@ package com.v2ray.ang.ui
import android.os.Bundle
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.handler.V2RayServiceManager
class ScSwitchActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -117,6 +117,8 @@ class ServerActivity : BaseActivity() {
private val container_short_id: LinearLayout? by lazy { findViewById(R.id.lay_short_id) }
private val et_spider_x: EditText? by lazy { findViewById(R.id.et_spider_x) }
private val container_spider_x: LinearLayout? by lazy { findViewById(R.id.lay_spider_x) }
private val et_mldsa65_verify: EditText? by lazy { findViewById(R.id.et_mldsa65_verify) }
private val container_mldsa65_verify: LinearLayout? by lazy { findViewById(R.id.lay_mldsa65_verify) }
private val et_reserved1: EditText? by lazy { findViewById(R.id.et_reserved1) }
private val et_local_address: EditText? by lazy { findViewById(R.id.et_local_address) }
private val et_local_mtu: EditText? by lazy { findViewById(R.id.et_local_mtu) }
@@ -253,9 +255,14 @@ class ServerActivity : BaseActivity() {
// Case 1: Null or blank
isBlank -> {
listOf(
container_sni, container_fingerprint, container_alpn,
container_allow_insecure, container_public_key,
container_short_id, container_spider_x
container_sni,
container_fingerprint,
container_alpn,
container_allow_insecure,
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
@@ -270,7 +277,8 @@ class ServerActivity : BaseActivity() {
listOf(
container_public_key,
container_short_id,
container_spider_x
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
@@ -284,7 +292,8 @@ class ServerActivity : BaseActivity() {
listOf(
container_public_key,
container_short_id,
container_spider_x
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.VISIBLE }
}
}
@@ -366,9 +375,12 @@ class ServerActivity : BaseActivity() {
if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure)
}
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
listOf(
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
} else if (config.security == REALITY) {
container_public_key?.visibility = View.VISIBLE
et_public_key?.text = Utils.getEditable(config.publicKey.orEmpty())
@@ -376,18 +388,23 @@ class ServerActivity : BaseActivity() {
et_short_id?.text = Utils.getEditable(config.shortId.orEmpty())
container_spider_x?.visibility = View.VISIBLE
et_spider_x?.text = Utils.getEditable(config.spiderX.orEmpty())
container_mldsa65_verify?.visibility = View.VISIBLE
et_mldsa65_verify?.text = Utils.getEditable(config.mldsa65Verify.orEmpty())
container_allow_insecure?.visibility = View.GONE
}
}
if (config.security.isNullOrEmpty()) {
container_sni?.visibility = View.GONE
container_fingerprint?.visibility = View.GONE
container_alpn?.visibility = View.GONE
container_allow_insecure?.visibility = View.GONE
container_public_key?.visibility = View.GONE
container_short_id?.visibility = View.GONE
container_spider_x?.visibility = View.GONE
listOf(
container_sni,
container_fingerprint,
container_alpn,
container_allow_insecure,
container_public_key,
container_short_id,
container_spider_x,
container_mldsa65_verify
).forEach { it?.visibility = View.GONE }
}
val network = Utils.arrayFind(networks, config.network.orEmpty())
if (network >= 0) {
@@ -550,6 +567,7 @@ class ServerActivity : BaseActivity() {
val publicKey = et_public_key?.text?.toString()
val shortId = et_short_id?.text?.toString()
val spiderX = et_spider_x?.text?.toString()
val mldsa65Verify = et_mldsa65_verify?.text?.toString()
val allowInsecure =
if (allowInsecureField == null || allowinsecures[allowInsecureField].isBlank()) {
@@ -566,6 +584,7 @@ class ServerActivity : BaseActivity() {
config.publicKey = publicKey
config.shortId = shortId
config.spiderX = spiderX
config.mldsa65Verify = mldsa65Verify
}
private fun transportTypes(network: String?): Array<out String> {

View File

@@ -18,7 +18,7 @@ import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R
import com.v2ray.ang.extension.toLongEx
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel
import java.util.concurrent.TimeUnit
@@ -242,7 +242,8 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_DOUBLE_COLUMN_DISPLAY,
AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_ALLOW_INSECURE
AppConfig.PREF_ALLOW_INSECURE,
AppConfig.PREF_USE_HEV_TUNNEL
).forEach { key ->
findPreference<CheckBoxPreference>(key)?.isChecked =
MmkvManager.decodeSettingsBool(key, false)
@@ -258,6 +259,7 @@ class SettingsActivity : BaseActivity() {
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_INTELLIGENT_SELECTION_METHOD,
AppConfig.PREF_MODE
).forEach { key ->
if (MmkvManager.decodeSettingsString(key) != null) {

View File

@@ -45,6 +45,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url)
binding.etFilter.text = Utils.getEditable(subItem.filter)
binding.etIntelligentSelectionFilter.text = Utils.getEditable(subItem.intelligentSelectionFilter)
binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl
@@ -60,6 +61,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = null
binding.etUrl.text = null
binding.etFilter.text = null
binding.etIntelligentSelectionFilter.text = null
binding.chkEnable.isChecked = true
binding.etPreProfile.text = null
binding.etNextProfile.text = null
@@ -75,6 +77,7 @@ class SubEditActivity : BaseActivity() {
subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString()
subItem.filter = binding.etFilter.text.toString()
subItem.intelligentSelectionFilter = binding.etIntelligentSelectionFilter.text.toString()
subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
subItem.prevProfile = binding.etPreProfile.text.toString()

View File

@@ -384,6 +384,29 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
MmkvManager.encodeServerList(serverList)
}
/**
* Creates an intelligent selection configuration containing all currently filtered servers.
*/
fun createIntelligentSelectionAll() {
viewModelScope.launch(Dispatchers.IO) {
val key = AngConfigManager.createIntelligentSelection(
getApplication<AngApplication>(),
serversCache.map { it.guid }.toList(),
subscriptionId
)
launch(Dispatchers.Main) {
if (key.isNullOrEmpty()) {
getApplication<AngApplication>().toastError(R.string.toast_failure)
} else {
getApplication<AngApplication>().toastSuccess(R.string.toast_success)
MmkvManager.setSelectServer(key)
reloadServerList()
}
}
}
}
/**
* Initializes assets.
* @param assets The asset manager.

View File

@@ -50,6 +50,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.PREF_SOCKS_PORT,
AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD,
AppConfig.PREF_INTELLIGENT_SELECTION_METHOD,
AppConfig.PREF_LANGUAGE,
AppConfig.PREF_UI_MODE_NIGHT,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
@@ -79,6 +80,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
AppConfig.SUBSCRIPTION_AUTO_UPDATE,
AppConfig.PREF_FRAGMENT_ENABLED,
AppConfig.PREF_MUX_ENABLED,
AppConfig.PREF_USE_HEV_TUNNEL
-> {
MmkvManager.encodeSettings(key, sharedPreferences.getBoolean(key, false))
}

View File

@@ -93,6 +93,25 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub_setting_intelligent_selection_filter" />
<EditText
android:id="@+id/et_intelligent_selection_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -178,4 +178,25 @@
android:nextFocusDown="@+id/sp_stream_fingerprint" />
</LinearLayout>
<LinearLayout
android:id="@+id/lay_mldsa65_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/padding_spacing_dp16"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_mldsa65_verify" />
<EditText
android:id="@+id/et_mldsa65_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:nextFocusDown="@+id/sp_stream_fingerprint" />
</LinearLayout>
</LinearLayout>

View File

@@ -82,6 +82,10 @@
android:icon="@drawable/ic_share_24dp"
android:title="@string/title_export_all"
app:showAsAction="never" />
<item
android:id="@+id/intelligent_selection_all"
android:title="@string/title_create_intelligent_selection_all_server"
app:showAsAction="never" />
<item
android:id="@+id/ping_all"
android:title="@string/title_ping_all_server"

View File

@@ -247,6 +247,8 @@
<string name="title_language">اللغة</string>
<string name="title_ui_settings">إعدادات واجهة المستخدم</string>
<string name="title_pref_ui_mode_night">إعدادات وضع واجهة المستخدم ليلاً</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">نسخ</string>
@@ -269,6 +271,7 @@
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
<string name="title_filter_config">تصفية ملف التكوين</string>
@@ -363,5 +366,12 @@
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id" translatable="false">শর্ট আইডি</string>
<string name="server_lab_spider_x" translatable="false">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key" translatable="false">সিক্রেট কী</string>
<string name="server_lab_reserved">সংরক্ষিত (ঐচ্ছিক)</string>
<string name="server_lab_local_address">স্থানীয় ঠিকানা (ঐচ্ছিক IPv4/IPv6, কমা দ্বারা পৃথক করা)</string>
@@ -247,6 +248,8 @@
<string name="title_language">ভাষা</string>
<string name="title_ui_settings">ইউআই সেটিংস</string>
<string name="title_pref_ui_mode_night">ইউআই মোড সেটিংস</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">লগক্যাট</string>
<string name="logcat_copy">কপি করুন</string>
@@ -269,6 +272,7 @@
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string>
<string name="title_filter_config">কনফিগারেশন ফাইল ফিল্টার করুন</string>
@@ -368,5 +372,12 @@
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string>
<string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">کیلیت سیخومی</string>
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما ز یک جوڌا ابۊن)</string>
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما ز یک جوڌا ابۊن)</string>
@@ -159,13 +160,13 @@
<item>گوم زیڌن</item>
</string-array>
<string name="title_pref_speed_enabled">ر وندن نشۉݩ داڌن سورعت</string>
<string name="summary_pref_speed_enabled">نشۉݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</string>
<string name="title_pref_speed_enabled">ر وندن نشووݩ داڌن سورعت</string>
<string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا. نماڌ وارسۊوی و ری و کار گرؽڌن آلشت ابۊ.</string>
<string name="title_pref_sniffing_enabled">ر وندن Sniffing</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff ن ز کتن امتهۉݩ کۊنین (پؽش فرز رۊشن)</string>
<string name="summary_pref_sniffing_enabled">دامنه sniff ن ز کتن امتهووݩ کۊنین (پؽش فرز رۊشن)</string>
<string name="title_pref_route_only_enabled">ر وندن routeOnly</string>
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی مۉرد نزرن و عونوان نشۊوی IP ووردارین.</string>
<string name="summary_pref_route_only_enabled">ز نوم دامنه sniffed تینا سی تور جوستن استفاڌه کۊنین وو نشۊوی موورد نزرن و عونوان نشۊوی IP ووردارین.</string>
<string name="title_pref_local_dns_enabled">ر وندن DNS مهلی</string>
<string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین فعال بۊ)</string>
@@ -176,18 +177,18 @@
<string name="title_pref_prefer_ipv6">ترجی IPv6</string>
<string name="summary_pref_prefer_ipv6">تورا IPv6 ن فعال کۊنین وو نشۊویا IPv6 ن ترجی بڌین</string>
<string name="title_pref_remote_dns">ز ر دیر (اختیاری) DNS (udp/tcp/https/quic) (اختیاری)</string>
<string name="title_pref_remote_dns">DNS ز ر دیر (اختیاری) (udp/tcp/https/quic) (اختیاری)</string>
<string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (تینا IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">ز شبکه مهلی اگوڌرته؟ VPN</string>
<string name="title_pref_vpn_bypass_lan">VPN ز شبکه مهلی اگوڌرته؟</string>
<string name="title_pref_vpn_interface_address">نشۊوی رابت VPN</string>
<string name="title_pref_domestic_dns">منی (اختیاری) DNS</string>
<string name="title_pref_domestic_dns">DNS منی (اختیاری)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_dns_hosts">هاست موستقیم (قالوو: دامنه: نشۊوی،...) DNS</string>
<string name="title_pref_dns_hosts">DNS هاست موستقیم (قالوو: دامنه: نشۊوی،...)</string>
<string name="summary_pref_dns_hosts">دامنه:نشۊوی،...</string>
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی (http/https)</string>
@@ -215,8 +216,8 @@
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
<string name="summary_pref_append_http_proxy">پروکسی HTTP ن موسقیمن ز (مۊرۊرگر/ی قرد ز برنومه یل لادراری بیڌه)، بؽ استفاڌه ز دسگا NIC مجازی (Android 10+) استفاڌه ابۊ.</string>
<string name="title_pref_double_column_display">ر وندن نشۉݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشۉݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
<string name="title_pref_double_column_display">ر وندن نشووݩ داڌن دو سۊتۊنی</string>
<string name="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ر وستن، وا برنومه ن ز نۊ ر ونین.</string>
<!-- AboutActivity -->
<string name="title_pref_feedback">فشناڌن منشڌ</string>
@@ -244,9 +245,11 @@
<string name="title_outbound_domain_resolve_method">بارت پؽش هل دامنه دری</string>
<string name="title_mode">هالت</string>
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</string>
<string name="title_language">زۉݩ</string>
<string name="title_language">زووݩ</string>
<string name="title_ui_settings">سامووا رابت منتوری</string>
<string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">داسووا</string>
<string name="logcat_copy">لف گیری</string>
@@ -269,6 +272,7 @@
<string name="title_sub_update">ورۊ کردن اشتراک جرگه سکویی</string>
<string name="title_ping_all_server">Tcping کانفیگا جرگه سکویی</string>
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا جرگه سکویی</string>
<string name="title_create_intelligent_selection_all_server">وورکل پسند هۊشمند کانفیگ جرگه سکویی</string>
<string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string>
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگا</string>
@@ -378,5 +382,12 @@
<item>هل وو ٱووردن و میزبووݩ یل دامنه DNS</item>
<item>هل وو جایونی دامنه</item>
</string-array>
<string name="intelligent_selection">پسند هۊشمند</string>
<string name="sub_setting_intelligent_selection_filter">گوڌنا دیاری پسند هۊشمند فیلتر مئمۊلی</string>
<string name="title_intelligent_selection_method">بارت پسند هۊشمند</string>
<string-array name="intelligent_selection_method">
<item>بلم ترین پینگ</item>
<item>هدقل بار</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
<string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">کلید خصوصی</string>
<string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
<string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string>
@@ -244,6 +245,8 @@
<string name="title_language">زبان</string>
<string name="title_ui_settings">تنظیمات رابط کاربری</string>
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">گزارشات</string>
<string name="logcat_copy">کپی</string>
@@ -266,6 +269,7 @@
<string name="title_sub_update">به‌روزرسانی گروه فعلی اشتراک</string>
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
<string name="title_create_intelligent_selection_all_server">ایجاد کانفیگ انتخاب هوشمند برای گروه فعلی</string>
<string name="title_user_asset_setting">فایل های منبع جغرافیایی</string>
<string name="title_sort_by_test_results">مرتب‌ سازی بر اساس نتایج آزمایش</string>
<string name="title_filter_config">فیلتر کردن کانفیگ‌ها</string>
@@ -377,5 +381,12 @@
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">انتخاب هوشمند</string>
<string name="sub_setting_intelligent_selection_filter">فیلتر نام مستعار برای انتخاب هوشمند</string>
<string name="title_intelligent_selection_method">روش انتخاب هوشمند</string>
<string-array name="intelligent_selection_method">
<item>کمترین پینگ</item>
<item>کمترین بار(لود)</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">Дополнительный ключ шифрования (необязательно)</string>
<string name="server_lab_short_id">ShortID</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">Закрытый ключ</string>
<string name="server_lab_reserved">Reserved (необязательно, через запятую)</string>
<string name="server_lab_local_address">Локальный адрес (необязательно, IPv4/IPv6 через запятую)</string>
@@ -153,9 +154,9 @@
<string name="title_pref_mux_xudp_concurency">XUDP-соединения (диапазон от 1 до 1024)</string>
<string name="title_pref_mux_xudp_quic">Обработка QUIC в мультиплексном туннеле</string>
<string-array name="mux_xudp_quic_entries">
<item>отклонять</item>
<item>разрешать</item>
<item>пропускать</item>
<item>Отклонять</item>
<item>Разрешать</item>
<item>Пропускать</item>
</string-array>
<string name="title_pref_speed_enabled">Отображение скорости</string>
@@ -179,7 +180,7 @@
<string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (только IPv4/v6)</string>
<string name="title_pref_vpn_bypass_lan">VPN пропускает LAN</string>
<string name="title_pref_vpn_bypass_lan">VPN обходит LAN</string>
<string name="title_pref_vpn_interface_address">VPN частный IP</string>
@@ -240,12 +241,14 @@
<string name="title_pref_auto_update_interval">Интервал автообновления (минут, не менее 15)</string>
<string name="title_core_loglevel">Подробность ведения журнала</string>
<string name="title_outbound_domain_resolve_method">Outbound domain pre-resolve method</string>
<string name="title_outbound_domain_resolve_method">Предварительное определение исходящего домена</string>
<string name="title_mode">Режим</string>
<string name="title_mode_help">Нажмите для получения дополнительной информации</string>
<string name="title_language">Язык</string>
<string name="title_ui_settings">Настройки интерфейса</string>
<string name="title_pref_ui_mode_night">Тема интерфейса</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">Журнал</string>
<string name="logcat_copy">Копировать</string>
@@ -266,10 +269,11 @@
<string name="sub_setting_next_profile">Следующая конфигурация прокси</string>
<string name="sub_setting_pre_profile_tip">Конфигурация должна быть уникальной</string>
<string name="title_sub_update">Обновить подписку группы</string>
<string name="title_ping_all_server">Проверка профилей группы</string>
<string name="title_ping_all_server">Проверить профили группы</string>
<string name="title_real_ping_all_server">Время отклика профилей группы</string>
<string name="title_create_intelligent_selection_all_server">Создать конфигурацию умного выбора</string>
<string name="title_user_asset_setting">Файлы ресурсов</string>
<string name="title_sort_by_test_results">Сортировка по результатам теста</string>
<string name="title_sort_by_test_results">Сортировать по результатам теста</string>
<string name="title_filter_config">Фильтр групп</string>
<string name="filter_config_all">Все группы</string>
<string name="title_del_duplicate_config_count">Удалено дубликатов профилей: %d</string>
@@ -368,14 +372,21 @@
<string-array name="vpn_bypass_lan">
<item>Как в профиле</item>
<item>Пропускает</item>
<item>Не пропускает</item>
<item>Обходит</item>
<item>Не обходит</item>
</string-array>
<string-array name="outbound_domain_resolve_method">
<item>Do not resolve</item>
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
<item>Не определять</item>
<item>Определять и добавлять к узлам DNS</item>
<item>Определять и заменять домен</item>
</string-array>
<string name="intelligent_selection">Умный выбор</string>
<string name="sub_setting_intelligent_selection_filter">Название фильтра умного выбора</string>
<string name="title_intelligent_selection_method">Принцип умного выбора</string>
<string-array name="intelligent_selection_method">
<item>Наименьшая задержка</item>
<item>Наименьшая нагрузка</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (Không bắt buộc)</string>
<string name="server_lab_local_address">Địa chỉ cục bộ (IPv4 / IPv6, phân cách bằng dấu phẩy)</string>
@@ -247,6 +248,8 @@
<string name="title_language">Ngôn ngữ</string>
<string name="title_ui_settings">Cài đặt UI</string>
<string name="title_pref_ui_mode_night">Cài đặt chế độ UI</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">Sao chép</string>
@@ -269,6 +272,7 @@
<string name="title_sub_update">Cập nhật các gói đăng ký</string>
<string name="title_ping_all_server">Ping tất cả máy chủ</string>
<string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
@@ -365,5 +369,12 @@
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (可选,逗号隔开)</string>
<string name="server_lab_local_address">本地地址 (可选 IPv4/IPv6逗号隔开)</string>
@@ -244,6 +245,8 @@
<string name="title_language">语言</string>
<string name="title_ui_settings">用户界面设置</string>
<string name="title_pref_ui_mode_night">界面颜色设置</string>
<string name="title_pref_use_hev_tunnel">启用新的 TUN 功能</string>
<string name="summary_pref_use_hev_tunnel">选择启用后 TUN 将使用 hev-socks5-tunnel 否则使用 badvpn-tun2socks</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</string>
@@ -266,6 +269,7 @@
<string name="title_sub_update">更新当前组订阅</string>
<string name="title_ping_all_server">测试当前组配置 Tcping</string>
<string name="title_real_ping_all_server">测试当前组配置真连接</string>
<string name="title_create_intelligent_selection_all_server">生成当前组智能选择配置</string>
<string name="title_user_asset_setting">资源文件</string>
<string name="title_sort_by_test_results">按测试结果排序</string>
<string name="title_filter_config">过滤配置文件</string>
@@ -369,5 +373,12 @@
<item>解析后添加至 DNS Hosts</item>
<item>解析后替换原域名</item>
</string-array>
<string name="intelligent_selection">智能选择</string>
<string name="sub_setting_intelligent_selection_filter">别名智能选择正则过滤</string>
<string name="title_intelligent_selection_method">智能选择方式</string>
<string-array name="intelligent_selection_method">
<item>最低延迟</item>
<item>最稳定</item>
</string-array>
</resources>

View File

@@ -80,6 +80,7 @@
<string name="server_lab_preshared_key">PreSharedKey (optional)</string>
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved (可選,逗號隔開)</string>
<string name="server_lab_local_address">本機位址 (可選 IPv4/IPv6逗號隔開)</string>
@@ -245,6 +246,8 @@
<string name="title_language">語言</string>
<string name="title_ui_settings">介面顏色設定</string>
<string name="title_pref_ui_mode_night">介面顯示模式</string>
<string name="title_pref_use_hev_tunnel">啟用新 TUN 功能</string>
<string name="summary_pref_use_hev_tunnel">選擇啟用後TUN 將使用 hev-socks5-tunnel否則使用 badvpn-tun2socks。</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</string>
@@ -267,6 +270,7 @@
<string name="title_sub_update">更新目前群組訂閱</string>
<string name="title_ping_all_server">偵測目前群組設定 Tcping</string>
<string name="title_real_ping_all_server">偵測目前群組設定真延遲</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">資源檔案</string>
<string name="title_sort_by_test_results">依偵測結果排序</string>
<string name="title_filter_config">過濾設定</string>
@@ -369,5 +373,12 @@
<item>解析後加入 DNS Hosts</item>
<item>解析後替換原網域名稱</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
</resources>

View File

@@ -208,4 +208,9 @@
<item>2</item>
</string-array>
<string-array name="intelligent_selection_method_value" translatable="false">
<item>0</item>
<item>1</item>
</string-array>
</resources>

View File

@@ -81,6 +81,7 @@
<string name="server_lab_preshared_key">PreSharedKey(optional)</string>
<string name="server_lab_short_id">ShortId</string>
<string name="server_lab_spider_x">SpiderX</string>
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
<string name="server_lab_secret_key">SecretKey</string>
<string name="server_lab_reserved">Reserved(Optional, separated by commas)</string>
<string name="server_lab_local_address">Local address (optional IPv4/IPv6, separated by commas)</string>
@@ -248,6 +249,8 @@
<string name="title_language">Language</string>
<string name="title_ui_settings">UI settings</string>
<string name="title_pref_ui_mode_night">UI mode settings</string>
<string name="title_pref_use_hev_tunnel">Enable New TUN Feature</string>
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use badvpn-tun2socks.</string>
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">Copy</string>
@@ -270,6 +273,7 @@
<string name="title_sub_update">Update current group subscription</string>
<string name="title_ping_all_server">Tcping current group configuration</string>
<string name="title_real_ping_all_server">Real delay current group configuration</string>
<string name="title_create_intelligent_selection_all_server">Creating Intelligent Selection Current Group Configuration</string>
<string name="title_user_asset_setting">Asset files</string>
<string name="title_sort_by_test_results">Sorting by test results</string>
<string name="title_filter_config">Filter configuration file</string>
@@ -379,5 +383,12 @@
<item>Resolve and add to DNS Hosts</item>
<item>Resolve and replace domain</item>
</string-array>
<string name="intelligent_selection">Intelligent Selection</string>
<string name="sub_setting_intelligent_selection_filter">Remarks Intelligent Selection regular filter</string>
<string name="title_intelligent_selection_method">Intelligent Selection Method</string>
<string-array name="intelligent_selection_method">
<item>Least Ping</item>
<item>Least Load</item>
</string-array>
</resources>

View File

@@ -13,6 +13,7 @@
android:key="pref_route_only_enabled"
android:summary="@string/summary_pref_route_only_enabled"
android:title="@string/title_pref_route_only_enabled" />
<CheckBoxPreference
android:key="pref_is_booted"
android:summary="@string/summary_pref_is_booted"
@@ -230,6 +231,14 @@
android:summary="%s"
android:title="@string/title_outbound_domain_resolve_method" />
<ListPreference
android:defaultValue="0"
android:entries="@array/intelligent_selection_method"
android:entryValues="@array/intelligent_selection_method_value"
android:key="pref_intelligent_selection_method"
android:summary="%s"
android:title="@string/title_intelligent_selection_method" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_advanced">
@@ -246,6 +255,11 @@
android:key="pref_mode"
android:summary="%s"
android:title="@string/title_mode" />
<CheckBoxPreference
android:key="pref_use_hev_tunnel"
android:summary="@string/summary_pref_use_hev_tunnel"
android:title="@string/title_pref_use_hev_tunnel" />
</PreferenceCategory>

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.10.1"
agp = "8.12.0"
desugarJdkLibs = "2.1.5"
gradleLicensePlugin = "0.9.8"
kotlin = "2.1.21"
@@ -21,7 +21,7 @@ toasty = "1.5.2"
editorkit = "2.9.0"
core = "3.5.3"
workRuntimeKtx = "2.10.2"
lifecycleViewmodelKtx = "2.9.1"
lifecycleViewmodelKtx = "2.9.2"
multidex = "2.0.1"
mockitoMockitoInline = "5.2.0"
flexbox = "3.0.0"