Introduce CoreConfigContext and refactor manager

Add CoreConfigContext DTO and CoreResolvedType enum, and implement CoreConfigContextBuilder to centralize profile resolution (CUSTOM, POLICYGROUP, PROXYCHAIN/NORMAL) with policy-group filters and proxy-chain ordering (next -> current -> prev).

Refactor CoreConfigManager to consume CoreConfigContext: replace prior branching with buildGroupConfig, buildProxyChainConfig and buildNormalConfig, unify CUSTOM handling, add postProcessForSpeedtest and toConfigResult helpers, and remove the old getMoreOutbounds logic. Adjust outbound assembly, DNS/route handling and speedtest post-processing accordingly.

Minor cleanup: centralize outbound conversion comments in CoreOutboundBuilder and tidy several condition checks and log lines. Overall this extracts resolution logic, reduces duplication and clarifies the runtime build flow.
This commit is contained in:
2dust committed 2026-05-02 10:53:53 +08:00
1 parent d0f1e21f49
commit dc1d3a27dd
5 files changed
+295 -233

No files matched your search

@@ -0,0 +1,114 @@
package com.v2ray.ang.core
import android.content.Context
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.CoreConfigContext
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.enums.CoreResolvedType
import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.Utils
/**
* Builds [com.v2ray.ang.dto.CoreConfigContext] from the selected profile.
* Keeps parsing and resolution logic out of [CoreConfigManager].
*/
object CoreConfigContextBuilder {
/** Loads profile by guid and returns a resolved runtime context. */
fun build(context: Context, guid: String): CoreConfigContext? {
val config = MmkvManager.decodeServerConfig(guid) ?: return null
return when (config.configType) {
EConfigType.CUSTOM -> CoreConfigContext(
context = context,
guid = guid,
selectedProfile = config,
resolvedProfiles = listOf(config),
resolvedType = CoreResolvedType.CUSTOM,
)
EConfigType.POLICYGROUP -> {
val profiles = resolvePolicyGroupProfiles(config)
CoreConfigContext(
context = context,
guid = guid,
selectedProfile = config,
resolvedProfiles = profiles,
resolvedType = CoreResolvedType.POLICYGROUP,
)
}
else -> {
val chainProfiles = resolveProxyChainProfiles(config)
CoreConfigContext(
context = context,
guid = guid,
selectedProfile = config,
resolvedProfiles = chainProfiles,
resolvedType = if (chainProfiles.size <= 1) CoreResolvedType.NORMAL else CoreResolvedType.PROXYCHAIN,
)
}
}
}
/** Resolves policy-group members with the same filters as runtime build. */
private fun resolvePolicyGroupProfiles(config: ProfileItem): List<ProfileItem> {
val serverList = MmkvManager.decodeAllServerList()
return serverList
.asSequence()
.mapNotNull { id -> MmkvManager.decodeServerConfig(id) }
.filter { profile ->
val subscriptionId = config.policyGroupSubscriptionId
if (subscriptionId.isNullOrBlank()) {
true
} else {
profile.subscriptionId == subscriptionId
}
}
.filter { profile ->
val filter = config.policyGroupFilter
if (filter.isNullOrBlank()) {
true
} else {
try {
Regex(filter).containsMatchIn(profile.remarks)
} catch (_: Exception) {
profile.remarks.contains(filter)
}
}
}
.filter { it.server.isNotNullEmpty() }
.filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) }
.filter { it.configType != EConfigType.CUSTOM }
.filter { it.configType != EConfigType.POLICYGROUP }
.toList()
}
/**
* Resolves chain nodes in fixed order: next -> current -> prev.
* If chain cannot be built, caller treats result as normal mode.
*/
private fun resolveProxyChainProfiles(config: ProfileItem): List<ProfileItem> {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
return listOf(config)
}
if (config.subscriptionId.isEmpty()) {
return listOf(config)
}
val subItem = MmkvManager.decodeSubscription(config.subscriptionId) ?: return listOf(config)
val resolved = mutableListOf<ProfileItem>()
// Keep the same practical chain order as current runtime assembly:
// next -> current -> prev
SettingsManager.getServerViaRemarks(subItem.nextProfile)?.let { resolved.add(it) }
resolved.add(config)
SettingsManager.getServerViaRemarks(subItem.prevProfile)?.let { resolved.add(it) }
return resolved
}
}
@@ -5,9 +5,11 @@ import android.text.TextUtils
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.ConfigResult import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.CoreConfigContext
import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.enums.CoreResolvedType
import com.v2ray.ang.enums.EConfigType import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.extension.isNotNullEmpty import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
@@ -25,7 +27,7 @@ object CoreConfigManager {
//region get config function //region get config function
/** /**
* Retrieves the V2ray configuration for the given GUID. * Builds normal runtime config JSON for the selected profile.
* *
* @param context The context of the caller. * @param context The context of the caller.
* @param guid The unique identifier for the V2ray configuration. * @param guid The unique identifier for the V2ray configuration.
@@ -33,14 +35,18 @@ object CoreConfigManager {
*/ */
fun getV2rayConfig(context: Context, guid: String): ConfigResult { fun getV2rayConfig(context: Context, guid: String): ConfigResult {
try { try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false) val configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
return if (config.configType == EConfigType.CUSTOM) { if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
getV2rayCustomConfig(context, guid, config) return getV2rayCustomConfig(configContext)
} else if (config.configType == EConfigType.POLICYGROUP) {
getV2rayGroupConfig(context, guid, config)
} else {
getV2rayNormalConfig(context, guid, config)
} }
val v2rayConfig = when (configContext.resolvedType) {
CoreResolvedType.POLICYGROUP -> buildGroupConfig(configContext)
CoreResolvedType.PROXYCHAIN -> buildProxyChainConfig(configContext)
CoreResolvedType.NORMAL -> buildNormalConfig(configContext)
CoreResolvedType.CUSTOM -> null
} ?: return ConfigResult(false)
return toConfigResult(configContext.guid, v2rayConfig)
} catch (e: Exception) { } catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config", e) LogUtil.e(AppConfig.TAG, "Failed to get V2ray config", e)
return ConfigResult(false) return ConfigResult(false)
@@ -48,7 +54,10 @@ object CoreConfigManager {
} }
/** /**
* Retrieves the speedtest V2ray configuration for the given GUID. * Builds speedtest config for the selected profile.
*
* It reuses the same build flow as normal config, then removes
* unnecessary sections for delay testing.
* *
* @param context The context of the caller. * @param context The context of the caller.
* @param guid The unique identifier for the V2ray configuration. * @param guid The unique identifier for the V2ray configuration.
@@ -56,15 +65,20 @@ object CoreConfigManager {
*/ */
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult { fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
try { try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false) val configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
return if (config.configType == EConfigType.CUSTOM) { if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
getV2rayCustomConfig(context, guid, config) return getV2rayCustomConfig(configContext)
} else if (config.configType == EConfigType.POLICYGROUP) {
// The number of policy groups will not be very large, so no special handling is needed.
getV2rayGroupConfig(context, guid, config)
} else {
getV2rayNormalConfig4Speedtest(context, guid, config)
} }
val v2rayConfig = when (configContext.resolvedType) {
CoreResolvedType.POLICYGROUP -> buildGroupConfig(configContext)
CoreResolvedType.PROXYCHAIN -> buildProxyChainConfig(configContext)
CoreResolvedType.NORMAL -> buildNormalConfig(configContext)
CoreResolvedType.CUSTOM -> null
} ?: return ConfigResult(false)
postProcessForSpeedtest(v2rayConfig)
return toConfigResult(configContext.guid, v2rayConfig)
} catch (e: Exception) { } catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e) LogUtil.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e)
return ConfigResult(false) return ConfigResult(false)
@@ -72,13 +86,11 @@ object CoreConfigManager {
} }
/** /**
* Retrieves the custom V2ray configuration. * Builds config result for CUSTOM profiles.
*
* @param guid The unique identifier for the V2ray configuration.
* @param config The profile item containing the configuration details.
* @return A ConfigResult object containing the result of the configuration retrieval.
*/ */
private fun getV2rayCustomConfig(context: Context, guid: String, config: ProfileItem): ConfigResult { private fun getV2rayCustomConfig(configContext: CoreConfigContext): ConfigResult {
val context = configContext.context
val guid = configContext.guid
val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false) val raw = MmkvManager.decodeServerRaw(guid) ?: return ConfigResult(false)
val result = ConfigResult(true, guid, raw) val result = ConfigResult(true, guid, raw)
if (!needTun()) { if (!needTun()) {
@@ -127,109 +139,11 @@ object CoreConfigManager {
return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result
} }
/** /** Builds full config for policy-group mode. */
* Retrieves the group V2ray configuration. private fun buildGroupConfig(configContext: CoreConfigContext): V2rayConfig? {
* val context = configContext.context
* @param context The context in which the function is called. val config = configContext.selectedProfile
* @param guid The unique identifier for the V2ray configuration. val validConfigs = configContext.resolvedProfiles
* @param config The profile item containing the configuration details.
* @return A ConfigResult object containing the result of the configuration retrieval.
*/
private fun getV2rayGroupConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
val result = ConfigResult(false)
val serverList = MmkvManager.decodeAllServerList()
val configList = serverList
.mapNotNull { id -> MmkvManager.decodeServerConfig(id) }
.filter { profile ->
val subscriptionId = config.policyGroupSubscriptionId
if (subscriptionId.isNullOrBlank()) {
true
} else {
profile.subscriptionId == subscriptionId
}
}
.filter { profile ->
val filter = config.policyGroupFilter
if (filter.isNullOrBlank()) {
true
} else {
try {
Regex(filter).containsMatchIn(profile.remarks)
} catch (e: Exception) {
profile.remarks.contains(filter)
}
}
}
val v2rayConfig = getV2rayMultipleConfig(context, config, configList) ?: return result
result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
result.guid = guid
return result
}
/**
* Retrieves the normal V2ray configuration.
*
* @param context The context in which the function is called.
* @param guid The unique identifier for the V2ray configuration.
* @param config The profile item containing the configuration details.
* @return A ConfigResult object containing the result of the configuration retrieval.
*/
private fun getV2rayNormalConfig(context: Context, guid: String, config: ProfileItem): ConfigResult {
val result = ConfigResult(false)
val address = config.server ?: return result
if (!Utils.isPureIpAddress(address)) {
if (!Utils.isValidUrl(address)) {
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
return result
}
}
val v2rayConfig = initV2rayConfig(context) ?: return result
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
getInbounds(v2rayConfig)
getOutbounds(v2rayConfig, config) ?: return result
getMoreOutbounds(v2rayConfig, config.subscriptionId)
getRouting(context, v2rayConfig)
getFakeDns(v2rayConfig)
getDns(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)
}
result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
result.guid = guid
return result
}
private fun getV2rayMultipleConfig(context: Context, config: ProfileItem, 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.POLICYGROUP }
.toList()
if (validConfigs.isEmpty()) { if (validConfigs.isEmpty()) {
LogUtil.w(AppConfig.TAG, "All configs are invalid") LogUtil.w(AppConfig.TAG, "All configs are invalid")
@@ -245,21 +159,18 @@ object CoreConfigManager {
v2rayConfig.outbounds.removeAt(0) v2rayConfig.outbounds.removeAt(0)
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>() val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
var index = 0 var index = 0
for (config in validConfigs) { for (item in validConfigs) {
index++ index++
val outbound = convertProfile2Outbound(config) ?: continue val outbound = convertProfile2Outbound(item) ?: continue
outbound.tag = "proxy-$index" outbound.tag = "proxy-$index-${item.remarks.trim()}"
outboundsList.add(outbound) outboundsList.add(outbound)
} }
outboundsList.addAll(v2rayConfig.outbounds) outboundsList.addAll(v2rayConfig.outbounds)
v2rayConfig.outbounds = ArrayList(outboundsList) v2rayConfig.outbounds = ArrayList(outboundsList)
getRouting(context, v2rayConfig) getRouting(context, v2rayConfig)
getFakeDns(v2rayConfig) getFakeDns(v2rayConfig)
getDns(v2rayConfig) getDns(v2rayConfig)
getBalance(v2rayConfig, config) getBalance(v2rayConfig, config)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) { if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
@@ -270,7 +181,7 @@ object CoreConfigManager {
v2rayConfig.policy = null v2rayConfig.policy = null
} }
//Resolve and add to DNS Hosts // Resolve and add to DNS Hosts
if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") { if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") == "1") {
resolveOutboundDomainsToHosts(v2rayConfig) resolveOutboundDomainsToHosts(v2rayConfig)
} }
@@ -279,29 +190,111 @@ object CoreConfigManager {
} }
/** /**
* Retrieves the normal V2ray configuration for speedtest. * Builds full V2Ray config for proxy-chain mode.
* *
* @param context The context in which the function is called. * Uses resolvedProfiles as an ordered chain and links each hop
* @param guid The unique identifier for the V2ray configuration. * with dialerProxy.
* @param config The profile item containing the configuration details.
* @return A ConfigResult object containing the result of the configuration retrieval.
*/ */
private fun getV2rayNormalConfig4Speedtest(context: Context, guid: String, config: ProfileItem): ConfigResult { private fun buildProxyChainConfig(configContext: CoreConfigContext): V2rayConfig? {
val result = ConfigResult(false) val context = configContext.context
val config = configContext.selectedProfile
val resolvedProfiles = configContext.resolvedProfiles
val address = config.server ?: return result val address = config.server ?: return null
if (!Utils.isPureIpAddress(address)) { if (!Utils.isPureIpAddress(address) && !Utils.isValidUrl(address)) {
if (!Utils.isValidUrl(address)) { LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain") return null
return result
}
} }
val v2rayConfig = initV2rayConfig(context) ?: return result val v2rayConfig = initV2rayConfig(context) ?: return null
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
getOutbounds(v2rayConfig, config) ?: return result getInbounds(v2rayConfig)
getMoreOutbounds(v2rayConfig, config.subscriptionId)
// Chain nodes are ordered as: next... -> current -> ...prev.
// Build and link the whole chain directly from resolvedProfiles.
val chainOutbounds = resolvedProfiles.mapNotNull { profile ->
convertProfile2Outbound(profile)
}.toMutableList()
if (chainOutbounds.size < 2) return null
chainOutbounds.forEachIndexed { index, outbound ->
outbound.tag = if (index == 0) AppConfig.TAG_PROXY else AppConfig.TAG_PROXY + index
}
for (index in 0 until chainOutbounds.size - 1) {
chainOutbounds[index].ensureSockopt().dialerProxy = chainOutbounds[index + 1].tag
}
// Keep built-in outbounds and place the chain before them.
val builtinOutbounds = if (v2rayConfig.outbounds.size > 1) {
v2rayConfig.outbounds.drop(1)
} else {
emptyList()
}
v2rayConfig.outbounds = ArrayList(chainOutbounds + builtinOutbounds)
getRouting(context, v2rayConfig)
getFakeDns(v2rayConfig)
getDns(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
}
/** Builds full config for normal single-node mode. */
private fun buildNormalConfig(configContext: CoreConfigContext): V2rayConfig? {
val context = configContext.context
val config = configContext.selectedProfile
val address = config.server ?: return null
if (!Utils.isPureIpAddress(address) && !Utils.isValidUrl(address)) {
LogUtil.w(AppConfig.TAG, "$address is an invalid ip or domain")
return null
}
val v2rayConfig = initV2rayConfig(context) ?: return null
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.remarks = config.remarks
getInbounds(v2rayConfig)
getOutbounds(v2rayConfig, config) ?: return null
getRouting(context, v2rayConfig)
getFakeDns(v2rayConfig)
getDns(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
}
/**
* Removes non-essential sections for speedtest use.
*/
private fun postProcessForSpeedtest(v2rayConfig: V2rayConfig) {
v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning" v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning"
v2rayConfig.inbounds.clear() v2rayConfig.inbounds.clear()
v2rayConfig.routing.rules.clear() v2rayConfig.routing.rules.clear()
@@ -309,15 +302,16 @@ object CoreConfigManager {
v2rayConfig.fakedns = null v2rayConfig.fakedns = null
v2rayConfig.stats = null v2rayConfig.stats = null
v2rayConfig.policy = null v2rayConfig.policy = null
v2rayConfig.outbounds.forEach { key -> key.mux = null }
}
v2rayConfig.outbounds.forEach { key -> /** Converts a built config object into a unified result payload. */
key.mux = null private fun toConfigResult(guid: String, v2rayConfig: V2rayConfig): ConfigResult {
} return ConfigResult(
status = true,
result.status = true guid = guid,
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: "" content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
result.guid = guid )
return result
} }
/** /**
@@ -801,60 +795,6 @@ object CoreConfigManager {
return true return true
} }
/**
* Configures additional outbound connections for proxy chaining.
*
* Sets up previous and next proxies in a subscription for advanced routing capabilities.
*
* @param v2rayConfig The V2ray configuration object to be modified
* @param subscriptionId The subscription ID to look up related proxies
* @return true if additional outbounds were configured successfully, false otherwise
*/
private fun getMoreOutbounds(v2rayConfig: V2rayConfig, subscriptionId: String): Boolean {
//fragment proxy
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false) == true) {
return false
}
if (subscriptionId.isEmpty()) {
return false
}
try {
val subItem = MmkvManager.decodeSubscription(subscriptionId) ?: return false
//current proxy
val outbound = v2rayConfig.outbounds[0]
//Previous proxy
val prevNode = SettingsManager.getServerViaRemarks(subItem.prevProfile)
if (prevNode != null) {
val prevOutbound = convertProfile2Outbound(prevNode)
if (prevOutbound != null) {
prevOutbound.tag = AppConfig.TAG_PROXY + "2"
v2rayConfig.outbounds.add(prevOutbound)
outbound.ensureSockopt().dialerProxy = prevOutbound.tag
}
}
//Next proxy
val nextNode = SettingsManager.getServerViaRemarks(subItem.nextProfile)
if (nextNode != null) {
val nextOutbound = convertProfile2Outbound(nextNode)
if (nextOutbound != null) {
nextOutbound.tag = AppConfig.TAG_PROXY
v2rayConfig.outbounds.add(0, nextOutbound)
outbound.tag = AppConfig.TAG_PROXY + "1"
nextOutbound.ensureSockopt().dialerProxy = outbound.tag
}
}
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to configure more outbounds", e)
return false
}
return true
}
/** /**
* Configures load balancing settings for the V2ray configuration. * Configures load balancing settings for the V2ray configuration.
* *
@@ -17,17 +17,12 @@ import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
/** /**
* Centralises all ProfileItem → OutboundBean conversion logic. * Centralizes ProfileItem -> OutboundBean conversion.
* * Most protocol builders mirror the previous *Fmt.toOutbound behavior.
* keeping every build method implementation identical to the original
* toOutbound implementations in each *Fmt class.
*/ */
object CoreOutboundBuilder { object CoreOutboundBuilder {
/** /** Dispatches a profile to protocol-specific outbound builder. */
* Dispatches [profileItem] to the appropriate protocol builder
* and returns the resulting [OutboundBean].
*/
fun convert(profileItem: ProfileItem): OutboundBean? { fun convert(profileItem: ProfileItem): OutboundBean? {
val outbound = when (profileItem.configType) { val outbound = when (profileItem.configType) {
EConfigType.VMESS -> toOutboundVmess(profileItem) EConfigType.VMESS -> toOutboundVmess(profileItem)
@@ -49,11 +44,7 @@ object CoreOutboundBuilder {
return outbound return outbound
} }
/** /** Applies global outbound options (mux, protocol-specific tweaks, etc.). */
* Updates outbound settings based on global preferences.
*
* Applies multiplexing and protocol-specific settings to an outbound connection.
*/
private fun updateOutboundWithGlobalSettings(outbound: OutboundBean): Boolean { private fun updateOutboundWithGlobalSettings(outbound: OutboundBean): Boolean {
try { try {
var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false) var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
@@ -126,14 +117,7 @@ object CoreOutboundBuilder {
return true return true
} }
/** /** Creates an initial outbound template for a protocol type. */
* Creates an initial outbound configuration for a specific protocol type.
*
* Provides a template configuration for different protocol types.
*
* @param configType The type of configuration to create
* @return An initial OutboundBean for the specified configuration type, or null for custom types
*/
fun createInitOutbound(configType: EConfigType): OutboundBean? { fun createInitOutbound(configType: EConfigType): OutboundBean? {
return when (configType) { return when (configType) {
EConfigType.VMESS, EConfigType.VMESS,
@@ -0,0 +1,15 @@
package com.v2ray.ang.dto
import android.content.Context
import com.v2ray.ang.enums.CoreResolvedType
/**
* Runtime context produced by the builder and consumed by CoreConfigManager.
*/
data class CoreConfigContext(
val context: Context,
val guid: String,
val selectedProfile: ProfileItem,
val resolvedProfiles: List<ProfileItem>,
val resolvedType: CoreResolvedType,
)
@@ -0,0 +1,9 @@
package com.v2ray.ang.enums
/** Runtime type used during config assembly only. */
enum class CoreResolvedType {
NORMAL,
POLICYGROUP,
PROXYCHAIN,
CUSTOM,
}