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.v2ray.ang.AppConfig
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.CoreConfigContext
import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.enums.CoreResolvedType
import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.extension.isNotNullEmpty
import com.v2ray.ang.handler.MmkvManager
@@ -25,7 +27,7 @@ object CoreConfigManager {
//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 guid The unique identifier for the V2ray configuration.
@@ -33,14 +35,18 @@ object CoreConfigManager {
*/
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
return if (config.configType == EConfigType.CUSTOM) {
getV2rayCustomConfig(context, guid, config)
} else if (config.configType == EConfigType.POLICYGROUP) {
getV2rayGroupConfig(context, guid, config)
} else {
getV2rayNormalConfig(context, guid, config)
val configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
return getV2rayCustomConfig(configContext)
}
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) {
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config", e)
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 guid The unique identifier for the V2ray configuration.
@@ -56,15 +65,20 @@ object CoreConfigManager {
*/
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false)
return if (config.configType == EConfigType.CUSTOM) {
getV2rayCustomConfig(context, guid, config)
} 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 configContext = CoreConfigContextBuilder.build(context, guid) ?: return ConfigResult(false)
if (configContext.resolvedType == CoreResolvedType.CUSTOM) {
return getV2rayCustomConfig(configContext)
}
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) {
LogUtil.e(AppConfig.TAG, "Failed to get V2ray config for speedtest", e)
return ConfigResult(false)
@@ -72,13 +86,11 @@ object CoreConfigManager {
}
/**
* Retrieves the custom V2ray configuration.
*
* @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.
* Builds config result for CUSTOM profiles.
*/
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 result = ConfigResult(true, guid, raw)
if (!needTun()) {
@@ -127,109 +139,11 @@ object CoreConfigManager {
return JsonUtil.toJsonPretty(json)?.let { ConfigResult(true, guid, it) } ?: result
}
/**
* Retrieves the group 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 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()
/** Builds full config for policy-group mode. */
private fun buildGroupConfig(configContext: CoreConfigContext): V2rayConfig? {
val context = configContext.context
val config = configContext.selectedProfile
val validConfigs = configContext.resolvedProfiles
if (validConfigs.isEmpty()) {
LogUtil.w(AppConfig.TAG, "All configs are invalid")
@@ -245,21 +159,18 @@ object CoreConfigManager {
v2rayConfig.outbounds.removeAt(0)
val outboundsList = mutableListOf<V2rayConfig.OutboundBean>()
var index = 0
for (config in validConfigs) {
for (item in validConfigs) {
index++
val outbound = convertProfile2Outbound(config) ?: continue
outbound.tag = "proxy-$index"
val outbound = convertProfile2Outbound(item) ?: continue
outbound.tag = "proxy-$index-${item.remarks.trim()}"
outboundsList.add(outbound)
}
outboundsList.addAll(v2rayConfig.outbounds)
v2rayConfig.outbounds = ArrayList(outboundsList)
getRouting(context, v2rayConfig)
getFakeDns(v2rayConfig)
getDns(v2rayConfig)
getBalance(v2rayConfig, config)
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) {
@@ -270,7 +181,7 @@ object CoreConfigManager {
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") {
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.
* @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.
* Uses resolvedProfiles as an ordered chain and links each hop
* with dialerProxy.
*/
private fun getV2rayNormalConfig4Speedtest(context: Context, guid: String, config: ProfileItem): ConfigResult {
val result = ConfigResult(false)
private fun buildProxyChainConfig(configContext: CoreConfigContext): V2rayConfig? {
val context = configContext.context
val config = configContext.selectedProfile
val resolvedProfiles = configContext.resolvedProfiles
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 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 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
getMoreOutbounds(v2rayConfig, config.subscriptionId)
getInbounds(v2rayConfig)
// 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.inbounds.clear()
v2rayConfig.routing.rules.clear()
@@ -309,15 +302,16 @@ object CoreConfigManager {
v2rayConfig.fakedns = null
v2rayConfig.stats = null
v2rayConfig.policy = null
v2rayConfig.outbounds.forEach { key -> key.mux = null }
}
v2rayConfig.outbounds.forEach { key ->
key.mux = null
}
result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
result.guid = guid
return result
/** Converts a built config object into a unified result payload. */
private fun toConfigResult(guid: String, v2rayConfig: V2rayConfig): ConfigResult {
return ConfigResult(
status = true,
guid = guid,
content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
)
}
/**
@@ -801,60 +795,6 @@ object CoreConfigManager {
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.
*
@@ -17,17 +17,12 @@ import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils
/**
* Centralises all ProfileItem → OutboundBean conversion logic.
*
* keeping every build method implementation identical to the original
* toOutbound implementations in each *Fmt class.
* Centralizes ProfileItem -> OutboundBean conversion.
* Most protocol builders mirror the previous *Fmt.toOutbound behavior.
*/
object CoreOutboundBuilder {
object CoreOutboundBuilder {
/**
* Dispatches [profileItem] to the appropriate protocol builder
* and returns the resulting [OutboundBean].
*/
/** Dispatches a profile to protocol-specific outbound builder. */
fun convert(profileItem: ProfileItem): OutboundBean? {
val outbound = when (profileItem.configType) {
EConfigType.VMESS -> toOutboundVmess(profileItem)
@@ -49,11 +44,7 @@ object CoreOutboundBuilder {
return outbound
}
/**
* Updates outbound settings based on global preferences.
*
* Applies multiplexing and protocol-specific settings to an outbound connection.
*/
/** Applies global outbound options (mux, protocol-specific tweaks, etc.). */
private fun updateOutboundWithGlobalSettings(outbound: OutboundBean): Boolean {
try {
var muxEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_MUX_ENABLED, false)
@@ -126,14 +117,7 @@ object CoreOutboundBuilder {
return true
}
/**
* 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
*/
/** Creates an initial outbound template for a protocol type. */
fun createInitOutbound(configType: EConfigType): OutboundBean? {
return when (configType) {
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,
}