Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92f33ee3f8 | ||
|
|
084346b348 | ||
|
|
15de18b736 | ||
|
|
b60423d1c0 | ||
|
|
86e38c6963 | ||
|
|
9ba4d7e691 | ||
|
|
16e72787c9 | ||
|
|
3ffac8b29f | ||
|
|
10df1b44ea | ||
|
|
fa12878258 | ||
|
|
0f1ea1e119 | ||
|
|
1959608f24 | ||
|
|
0e041a6e9a | ||
|
|
c78ef380cc | ||
|
|
57362a4bde | ||
|
|
7f24ad534f | ||
|
|
680832614b | ||
|
|
4357abbff4 | ||
|
|
905be66c3f | ||
|
|
318a7b54a5 | ||
|
|
5db2df77a0 | ||
|
|
d039cb9edf | ||
|
|
9a1654bae9 |
Submodule AndroidLibXrayLite updated: 8ad3e1ddf1...81aa9af4dd
@@ -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(';')
|
||||
|
||||
BIN
V2rayNG/app/libs/arm64-v8a/libhev-socks5-tunnel.so
Normal file
BIN
V2rayNG/app/libs/arm64-v8a/libhev-socks5-tunnel.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/libs/armeabi-v7a/libhev-socks5-tunnel.so
Normal file
BIN
V2rayNG/app/libs/armeabi-v7a/libhev-socks5-tunnel.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/libs/x86/libhev-socks5-tunnel.so
Normal file
BIN
V2rayNG/app/libs/x86/libhev-socks5-tunnel.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/libs/x86_64/libhev-socks5-tunnel.so
Normal file
BIN
V2rayNG/app/libs/x86_64/libhev-socks5-tunnel.so
Normal file
Binary file not shown.
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
@@ -137,4 +138,4 @@ object PluginUtil {
|
||||
Log.e(AppConfig.TAG, "Failed to stop Hysteria2 process", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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,14 +363,14 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
if (Utils.isPureIpAddress(it)) {
|
||||
builder.addDnsServer(it)
|
||||
}
|
||||
// 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()
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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_PREFER_IPV6)) {
|
||||
cmd.add("--netif-ip6addr")
|
||||
cmd.add(vpnConfig.ipv6Router)
|
||||
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_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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
2
hysteria
2
hysteria
Submodule hysteria updated: 2adeec2900...5f3c47e6c3
Reference in New Issue
Block a user