Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef13e336da | |||
| d16cb21059 | |||
| 237837e7be | |||
| 2a6edd98be | |||
| 9238b80675 | |||
| bb5e6f1959 | |||
| 1cc0865411 | |||
| cb8d979f23 | |||
| 5932b0db29 | |||
| dbe4fe78b5 |
+1
-1
Submodule AndroidLibXrayLite updated: 1314c9af79...4c3a3cd051
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 727
|
||||
versionName = "2.1.7"
|
||||
versionCode = 728
|
||||
versionName = "2.1.8"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
|
||||
@@ -180,6 +180,8 @@ object AppConfig {
|
||||
const val CUSTOM = ""
|
||||
const val SHADOWSOCKS = "ss://"
|
||||
const val SOCKS = "socks://"
|
||||
const val SOCKS4 = "socks4://"
|
||||
const val SOCKS5 = "socks5://"
|
||||
const val HTTP = "http://"
|
||||
const val VLESS = "vless://"
|
||||
const val TROJAN = "trojan://"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.contracts
|
||||
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
|
||||
interface MainAdapterListener : BaseAdapterListener {
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.core
|
||||
import android.content.Context
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.CoreConfigContext
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.CoreResolvedType
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
|
||||
@@ -6,8 +6,8 @@ import com.google.gson.JsonArray
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ConfigResult
|
||||
import com.v2ray.ang.dto.CoreConfigContext
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.RulesetItem
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.enums.CoreResolvedType
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.core
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.V2rayConfig.OutboundBean
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
|
||||
@@ -13,7 +13,8 @@ import androidx.core.content.ContextCompat
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.ServiceControl
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.OutboundTrafficStat
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
@@ -257,7 +258,7 @@ object CoreServiceManager {
|
||||
}
|
||||
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||
NotificationManager.startSpeedNotification(currentConfig)
|
||||
NotificationManager.startSpeedNotification()
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Core started successfully")
|
||||
}
|
||||
|
||||
@@ -299,13 +300,32 @@ object CoreServiceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the statistics for a given tag and link.
|
||||
* @param tag The tag to query.
|
||||
* @param link The link to query.
|
||||
* @return The statistics value.
|
||||
* Queries and resets all outbound traffic counters in one core call.
|
||||
* Go side format: tag,direction,value;tag,direction,value;
|
||||
*/
|
||||
fun queryStats(tag: String, link: String): Long {
|
||||
return coreController.queryStats(tag, link)
|
||||
fun queryAllOutboundTrafficStats(): List<OutboundTrafficStat> {
|
||||
val payload = coreController.queryAllOutboundTrafficStats()
|
||||
|
||||
val result = ArrayList<OutboundTrafficStat>()
|
||||
|
||||
payload.split(';').forEach { entry ->
|
||||
if (entry.isBlank()) return@forEach
|
||||
|
||||
val parts = entry.split(',', limit = 3)
|
||||
if (parts.size != 3) return@forEach
|
||||
|
||||
val value = parts[2].toLongOrNull() ?: return@forEach
|
||||
|
||||
result.add(
|
||||
OutboundTrafficStat(
|
||||
tag = parts[0],
|
||||
direction = parts[1],
|
||||
value = value,
|
||||
)
|
||||
)
|
||||
}
|
||||
// LogUtil.d(AppConfig.TAG, "Queried outbound traffic stats: $result")
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,12 +508,12 @@ object CoreServiceManager {
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Screen off")
|
||||
NotificationManager.stopSpeedNotification(currentConfig)
|
||||
NotificationManager.stopSpeedNotification()
|
||||
}
|
||||
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Screen on")
|
||||
NotificationManager.startSpeedNotification(currentConfig)
|
||||
NotificationManager.startSpeedNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
import android.content.Context
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.CoreResolvedType
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class OutboundTrafficStat(
|
||||
val tag: String,
|
||||
val direction: String,
|
||||
val value: Long,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
data class UrlContentRequest(
|
||||
val url: String?,
|
||||
val timeout: Int = 15000,
|
||||
val httpPort: Int = 0,
|
||||
val proxyUsername: String? = null,
|
||||
val proxyPassword: String? = null,
|
||||
val userAgent: String? = null
|
||||
)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class AssetUrlCache(
|
||||
val guid: String,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class AssetUrlItem(
|
||||
var remarks: String = "",
|
||||
+3
-11
@@ -1,10 +1,6 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.AppConfig.PORT_SOCKS
|
||||
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
@@ -80,13 +76,9 @@ data class ProfileItem(
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllOutboundTags(): MutableList<String> {
|
||||
return mutableListOf(TAG_PROXY, TAG_DIRECT, TAG_BLOCKED)
|
||||
}
|
||||
|
||||
fun getServerAddressAndPort(): String {
|
||||
if (server.isNullOrEmpty() && configType == EConfigType.CUSTOM) {
|
||||
return "$LOOPBACK:$PORT_SOCKS"
|
||||
return "${AppConfig.LOOPBACK}:${AppConfig.PORT_SOCKS}"
|
||||
}
|
||||
return Utils.getIpv6Address(server) + ":" + serverPort
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class RulesetItem(
|
||||
var remarks: String? = "",
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
|
||||
fun getTestDelayString(): String {
|
||||
@@ -7,4 +7,4 @@ data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
|
||||
}
|
||||
return testDelayMillis.toString() + "ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class ServersCache(
|
||||
val guid: String,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class SubscriptionCache(
|
||||
val guid: String,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class SubscriptionItem(
|
||||
var remarks: String = "",
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package com.v2ray.ang.dto
|
||||
package com.v2ray.ang.dto.entities
|
||||
|
||||
data class WebDavConfig(
|
||||
val baseUrl: String,
|
||||
@@ -6,4 +6,4 @@ data class WebDavConfig(
|
||||
val password: String? = null,
|
||||
val remoteBasePath: String = "/",
|
||||
val timeoutSeconds: Long = 30
|
||||
)
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.v2ray.ang.fmt
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.VmessQRCode
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.HY2
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.core.CoreConfigManager
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.SubscriptionCache
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.dto.SubscriptionUpdateResult
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.fmt.CustomFmt
|
||||
@@ -30,6 +30,21 @@ import java.net.URI
|
||||
|
||||
object AngConfigManager {
|
||||
|
||||
// Parser mapping for different config types (lazy initialized)
|
||||
private val configFmtParsers: Map<String, (String) -> ProfileItem?> by lazy {
|
||||
mapOf(
|
||||
EConfigType.VMESS.protocolScheme to VmessFmt::parse,
|
||||
EConfigType.SHADOWSOCKS.protocolScheme to ShadowsocksFmt::parse,
|
||||
EConfigType.SOCKS.protocolScheme to SocksFmt::parse,
|
||||
AppConfig.SOCKS4 to SocksFmt::parse,
|
||||
AppConfig.SOCKS5 to SocksFmt::parse,
|
||||
EConfigType.TROJAN.protocolScheme to TrojanFmt::parse,
|
||||
EConfigType.VLESS.protocolScheme to VlessFmt::parse,
|
||||
EConfigType.WIREGUARD.protocolScheme to WireguardFmt::parse,
|
||||
EConfigType.HYSTERIA2.protocolScheme to Hysteria2Fmt::parse,
|
||||
AppConfig.HY2 to Hysteria2Fmt::parse
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the configuration to the clipboard.
|
||||
@@ -310,6 +325,16 @@ object AngConfigManager {
|
||||
private fun findMatchedProfileKey(keyToProfile: Map<String, ProfileItem>, target: ProfileItem?): String? {
|
||||
if (keyToProfile.isEmpty() || target == null) return null
|
||||
|
||||
// Level 0: Full match (remarks + server + port + password)
|
||||
if (target.remarks.isNotBlank()) {
|
||||
keyToProfile.entries.firstOrNull { (_, saved) ->
|
||||
isSameText(saved.remarks, target.remarks) &&
|
||||
isSameText(saved.server, target.server) &&
|
||||
isSameText(saved.serverPort, target.serverPort) &&
|
||||
isSameText(saved.password, target.password)
|
||||
}?.key?.let { return it }
|
||||
}
|
||||
|
||||
// Level 1: Match by remarks
|
||||
if (target.remarks.isNotBlank()) {
|
||||
keyToProfile.entries.firstOrNull { (_, saved) ->
|
||||
@@ -442,22 +467,8 @@ object AngConfigManager {
|
||||
return null
|
||||
}
|
||||
|
||||
val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
||||
VmessFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||
ShadowsocksFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
||||
SocksFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
||||
TrojanFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
||||
VlessFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.WIREGUARD.protocolScheme)) {
|
||||
WireguardFmt.parse(str)
|
||||
} else if (str.startsWith(EConfigType.HYSTERIA2.protocolScheme) || str.startsWith(HY2)) {
|
||||
Hysteria2Fmt.parse(str)
|
||||
} else {
|
||||
null
|
||||
val config = configFmtParsers.firstNotNullOfOrNull { (scheme, parser) ->
|
||||
if (str.startsWith(scheme)) parser(str) else null
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
@@ -535,14 +546,28 @@ object AngConfigManager {
|
||||
|
||||
var configText = try {
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent, 15000, httpPort, proxyUsername, proxyPassword)
|
||||
HttpUtil.getUrlContentWithUserAgent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
userAgent = userAgent,
|
||||
timeout = 15000,
|
||||
httpPort = httpPort,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
|
||||
""
|
||||
}
|
||||
if (configText.isEmpty()) {
|
||||
configText = try {
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent)
|
||||
HttpUtil.getUrlContentWithUserAgent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
userAgent = userAgent
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
|
||||
""
|
||||
|
||||
@@ -4,14 +4,14 @@ import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig.DEFAULT_SUBSCRIPTION_ID
|
||||
import com.v2ray.ang.AppConfig.PREF_IS_BOOTED
|
||||
import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET
|
||||
import com.v2ray.ang.dto.AssetUrlCache
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.SubscriptionCache
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.dto.WebDavConfig
|
||||
import com.v2ray.ang.dto.entities.AssetUrlCache
|
||||
import com.v2ray.ang.dto.entities.AssetUrlItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.RulesetItem
|
||||
import com.v2ray.ang.dto.entities.ServerAffiliationInfo
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.dto.entities.WebDavConfig
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.core.app.NotificationCompat
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.core.CoreServiceManager
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
@@ -43,53 +43,15 @@ object NotificationManager {
|
||||
* Starts the speed notification.
|
||||
* @param currentConfig The current profile configuration.
|
||||
*/
|
||||
fun startSpeedNotification(currentConfig: ProfileItem?) {
|
||||
fun startSpeedNotification() {
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) return
|
||||
if (speedNotificationJob != null || CoreServiceManager.isRunning() == false) return
|
||||
|
||||
var lastZeroSpeed = false
|
||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||
outboundTags?.remove(AppConfig.TAG_DIRECT)
|
||||
|
||||
speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
while (isActive) {
|
||||
val queryTime = System.currentTimeMillis()
|
||||
val sinceLastQueryIn = (queryTime - lastQueryTime)
|
||||
|
||||
// If the query interval is too short, skip this round to avoid excessive CPU usage
|
||||
if (sinceLastQueryIn < QUERY_INTERVAL_MS) {
|
||||
LogUtil.w(AppConfig.TAG, "Query interval too short: ${sinceLastQueryIn}ms, skipping")
|
||||
lastQueryTime = queryTime
|
||||
delay(QUERY_INTERVAL_MS)
|
||||
continue
|
||||
}
|
||||
val sinceLastQueryInSeconds = sinceLastQueryIn / 1000.0
|
||||
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags?.forEach {
|
||||
val up = CoreServiceManager.queryStats(it, AppConfig.UPLINK)
|
||||
val down = CoreServiceManager.queryStats(it, AppConfig.DOWNLINK)
|
||||
if (up + down > 0) {
|
||||
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
||||
proxyTotal += up + down
|
||||
}
|
||||
}
|
||||
val directUplink = CoreServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
|
||||
val directDownlink = CoreServiceManager.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, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds
|
||||
)
|
||||
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
||||
}
|
||||
lastZeroSpeed = zeroSpeed
|
||||
lastQueryTime = queryTime
|
||||
lastZeroSpeed = updateSpeedNotificationOnce(lastZeroSpeed)
|
||||
delay(QUERY_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
@@ -168,13 +130,12 @@ object NotificationManager {
|
||||
|
||||
/**
|
||||
* Stops the speed notification.
|
||||
* @param currentConfig The current profile configuration.
|
||||
*/
|
||||
fun stopSpeedNotification(currentConfig: ProfileItem?) {
|
||||
fun stopSpeedNotification() {
|
||||
speedNotificationJob?.let {
|
||||
it.cancel()
|
||||
speedNotificationJob = null
|
||||
updateNotification(currentConfig?.remarks, 0, 0)
|
||||
updateNotification("", 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +208,69 @@ object NotificationManager {
|
||||
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the speed notification once.
|
||||
* Queries traffic stats, separates proxy and direct, and updates the notification.
|
||||
* @param lastZeroSpeed The previous zero speed state.
|
||||
* @return The current zero speed state.
|
||||
*/
|
||||
private fun updateSpeedNotificationOnce(lastZeroSpeed: Boolean): Boolean {
|
||||
val queryTime = System.currentTimeMillis()
|
||||
val sinceLastQueryIn = (queryTime - lastQueryTime)
|
||||
|
||||
// If the query interval is too short, skip this round to avoid excessive CPU usage
|
||||
if (sinceLastQueryIn < QUERY_INTERVAL_MS) {
|
||||
LogUtil.w(AppConfig.TAG, "Query interval too short: ${sinceLastQueryIn}ms, skipping")
|
||||
lastQueryTime = queryTime
|
||||
return lastZeroSpeed
|
||||
}
|
||||
val sinceLastQueryInSeconds = sinceLastQueryIn / 1000.0
|
||||
|
||||
var proxyUplink = 0L
|
||||
var proxyDownlink = 0L
|
||||
var directUplink = 0L
|
||||
var directDownlink = 0L
|
||||
|
||||
CoreServiceManager.queryAllOutboundTrafficStats().forEach { stat ->
|
||||
when {
|
||||
stat.tag == AppConfig.TAG_DIRECT -> {
|
||||
when (stat.direction) {
|
||||
AppConfig.UPLINK -> directUplink += stat.value
|
||||
AppConfig.DOWNLINK -> directDownlink += stat.value
|
||||
}
|
||||
}
|
||||
|
||||
stat.tag.startsWith(AppConfig.TAG_PROXY) -> {
|
||||
when (stat.direction) {
|
||||
AppConfig.UPLINK -> proxyUplink += stat.value
|
||||
AppConfig.DOWNLINK -> proxyDownlink += stat.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val proxyTotal = proxyUplink + proxyDownlink
|
||||
val directTotal = directUplink + directDownlink
|
||||
val zeroSpeed = proxyTotal + directTotal == 0L
|
||||
if (!zeroSpeed || !lastZeroSpeed) {
|
||||
val text = StringBuilder()
|
||||
appendSpeedString(
|
||||
text, AppConfig.TAG_PROXY,
|
||||
proxyUplink / sinceLastQueryInSeconds,
|
||||
proxyDownlink / sinceLastQueryInSeconds
|
||||
)
|
||||
|
||||
appendSpeedString(
|
||||
text, AppConfig.TAG_DIRECT,
|
||||
directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds
|
||||
)
|
||||
updateNotification(text.toString(), proxyTotal, directTotal)
|
||||
}
|
||||
lastQueryTime = queryTime
|
||||
return zeroSpeed
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service instance.
|
||||
* @return The service instance.
|
||||
|
||||
@@ -12,9 +12,9 @@ import com.v2ray.ang.AppConfig.GEOIP_PRIVATE
|
||||
import com.v2ray.ang.AppConfig.GEOSITE_PRIVATE
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.AppConfig.VPN
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.RulesetItem
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.Language
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.v2ray.ang.handler
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.IPAPIInfo
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
@@ -88,7 +89,15 @@ object SpeedtestManager {
|
||||
val proxyPassword = SettingsManager.getSocksPassword()
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
if (httpPort == 0) return null
|
||||
val content = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword) ?: return null
|
||||
val content = HttpUtil.getUrlContent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
timeout = 5000,
|
||||
httpPort = httpPort,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword
|
||||
)
|
||||
) ?: return null
|
||||
val ipInfo = JsonUtil.fromJson(content, IPAPIInfo::class.java) ?: return null
|
||||
|
||||
val ip = listOf(
|
||||
|
||||
@@ -13,7 +13,7 @@ import androidx.work.workDataOf
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.enums.NotificationChannelType
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.NotificationHelper
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.dto.CheckUpdateResult
|
||||
import com.v2ray.ang.dto.GitHubRelease
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.extension.concatUrl
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
@@ -23,10 +24,23 @@ object UpdateCheckerManager {
|
||||
val proxyUsername = SettingsManager.getSocksUsername()
|
||||
val proxyPassword = SettingsManager.getSocksPassword()
|
||||
|
||||
var response = HttpUtil.getUrlContent(url, 5000)
|
||||
var response = HttpUtil.getUrlContent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
timeout = 5000
|
||||
)
|
||||
)
|
||||
if (response.isNullOrEmpty()) {
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
response = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword)
|
||||
response = HttpUtil.getUrlContent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
timeout = 5000,
|
||||
httpPort = httpPort,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword
|
||||
)
|
||||
)
|
||||
?: throw IllegalStateException("Failed to get response")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.handler
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.WebDavConfig
|
||||
import com.v2ray.ang.dto.entities.WebDavConfig
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -61,6 +61,45 @@ class DialerNativeService : IDialerService {
|
||||
private const val UNARY_BODY_WAIT_TIMEOUT_MS = 15_000L
|
||||
private val TOKEN_REGEX = Regex("""/websocket\?token=([^"'\s]+)""")
|
||||
private val METHODS_WITHOUT_BODY = setOf("GET", "HEAD")
|
||||
private val HEADERS_BLACKLIST = hashSetOf(
|
||||
// AI suggest:
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"content-encoding",
|
||||
"connection",
|
||||
"upgrade",
|
||||
"sec-websocket-key",
|
||||
"sec-websocket-version",
|
||||
"sec-websocket-protocol",
|
||||
"Timing-Allow-Origin",
|
||||
// xray
|
||||
"Set-Cookie",
|
||||
"Cookie",
|
||||
"Origin",
|
||||
"Sec-CH-UA",
|
||||
"Sec-CH-UA-Mobile",
|
||||
"Sec-CH-UA-Platform",
|
||||
"DNT",
|
||||
"User-Agent",
|
||||
"Accept-Language",
|
||||
"Cache-Control",
|
||||
"Upgrade-Insecure-Requests",
|
||||
"Sec-Fetch-Site",
|
||||
"Sec-Fetch-Mode",
|
||||
"Sec-Fetch-User",
|
||||
"Sec-Fetch-Dest",
|
||||
"Referer",
|
||||
"Accept",
|
||||
"Priority",
|
||||
"Pragma",
|
||||
"Access-Control-Allow-Origin",
|
||||
"Access-Control-Allow-Credentials",
|
||||
"Access-Control-Allow-Methods",
|
||||
"Access-Control-Allow-Headers",
|
||||
"Access-Control-Expose-Headers",
|
||||
"Access-Control-Max-Age",
|
||||
)
|
||||
}
|
||||
|
||||
@Volatile
|
||||
@@ -630,6 +669,11 @@ class DialerNativeService : IDialerService {
|
||||
requestBuilder.header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
task.extra.referrer?.takeIf { it.isNotBlank() }
|
||||
?.let { requestBuilder.header("Referer", it) }
|
||||
val taskHeaders = task.extra.headers.filterKeys { key ->
|
||||
val lowerKey = key.lowercase()
|
||||
!HEADERS_BLACKLIST.any { blackKey -> blackKey.equals(lowerKey, ignoreCase = true) }
|
||||
}
|
||||
taskHeaders.forEach { (key, value) -> requestBuilder.header(key, value) }
|
||||
|
||||
val method = task.method.uppercase()
|
||||
val methodAllowsBody = method !in METHODS_WITHOUT_BODY
|
||||
@@ -663,7 +707,7 @@ class DialerNativeService : IDialerService {
|
||||
val extra: Extra
|
||||
) {
|
||||
data class Extra(
|
||||
// val headers: Map<String, String> = emptyMap(),
|
||||
val headers: Map<String, String> = emptyMap(),
|
||||
// val cookies: Map<String, String> = emptyMap(),
|
||||
val protocols: List<String> = emptyList(),
|
||||
val referrer: String? = null
|
||||
@@ -679,7 +723,7 @@ class DialerNativeService : IDialerService {
|
||||
|
||||
val streamResponse = root.optBoolean("streamResponse", false)
|
||||
val extraObject = root.optJSONObject("extra")
|
||||
// val headers = extraObject.optStringMap("headers")
|
||||
val headers = extraObject.optStringMap("headers")
|
||||
// val cookies = extraObject.optStringMap("cookies")
|
||||
val referrer = extraObject?.optString("referrer")?.takeIf { it.isNotBlank() }
|
||||
val protocols = extraObject.optProtocols()
|
||||
@@ -689,7 +733,7 @@ class DialerNativeService : IDialerService {
|
||||
url = url,
|
||||
streamResponse = streamResponse,
|
||||
extra = Extra(
|
||||
// headers = headers,
|
||||
headers = headers,
|
||||
// cookies = cookies,
|
||||
protocols = protocols,
|
||||
referrer = referrer
|
||||
@@ -698,16 +742,16 @@ class DialerNativeService : IDialerService {
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
// private fun JSONObject?.optStringMap(name: String): Map<String, String> {
|
||||
// val child = this?.optJSONObject(name) ?: return emptyMap()
|
||||
// val map = LinkedHashMap<String, String>()
|
||||
// val iter = child.keys()
|
||||
// while (iter.hasNext()) {
|
||||
// val key = iter.next()
|
||||
// map[key] = child.optString(key)
|
||||
// }
|
||||
// return map
|
||||
// }
|
||||
private fun JSONObject?.optStringMap(name: String): Map<String, String> {
|
||||
val child = this?.optJSONObject(name) ?: return emptyMap()
|
||||
val map = LinkedHashMap<String, String>()
|
||||
val iter = child.keys()
|
||||
while (iter.hasNext()) {
|
||||
val key = iter.next()
|
||||
map[key] = child.optString(key)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
private fun JSONObject?.optProtocols(): List<String> {
|
||||
val raw = this?.opt("protocol") ?: return emptyList()
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityBackupBinding
|
||||
import com.v2ray.ang.databinding.DialogWebdavBinding
|
||||
import com.v2ray.ang.dto.WebDavConfig
|
||||
import com.v2ray.ang.dto.entities.WebDavConfig
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.MainAdapterListener
|
||||
import com.v2ray.ang.databinding.FragmentGroupServerBinding
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
|
||||
@@ -12,8 +12,8 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.MainAdapterListener
|
||||
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.ServersCache
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ServersCache
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityBypassListBinding
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
@@ -189,12 +190,25 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
val url = AppConfig.ANDROID_PACKAGE_NAME_LIST_URL
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
var content = HttpUtil.getUrlContent(url, 5000)
|
||||
var content = HttpUtil.getUrlContent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
timeout = 5000
|
||||
)
|
||||
)
|
||||
if (content.isNullOrEmpty()) {
|
||||
val proxyUsername = SettingsManager.getSocksUsername()
|
||||
val proxyPassword = SettingsManager.getSocksPassword()
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
content = HttpUtil.getUrlContent(url, 5000, httpPort, proxyUsername, proxyPassword) ?: ""
|
||||
content = HttpUtil.getUrlContent(
|
||||
UrlContentRequest(
|
||||
url = url,
|
||||
timeout = 5000,
|
||||
httpPort = httpPort,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword
|
||||
)
|
||||
) ?: ""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
//LogUtil.i(AppConfig.TAG, content)
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.v2ray.ang.AppConfig.BUILTIN_OUTBOUND_TAGS
|
||||
import com.v2ray.ang.AppConfig.TAG_PROXY
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityRoutingEditBinding
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.entities.RulesetItem
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.v2ray.ang.AppConfig.TLS
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
|
||||
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerGroupBinding
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.extension.toast
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.BaseAdapterListener
|
||||
import com.v2ray.ang.databinding.ActivityServerProxyChainBinding
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
|
||||
@@ -4,12 +4,15 @@ import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.ImageButton
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
@@ -33,6 +36,7 @@ class SubEditActivity : BaseActivity() {
|
||||
//setContentView(binding.root)
|
||||
setContentViewWithToolbar(binding.root, showHomeAsUp = true, title = getString(R.string.title_sub_setting))
|
||||
|
||||
setupProfileRemarkInputs()
|
||||
SettingsChangeManager.makeSetupGroupTab()
|
||||
val subItem = MmkvManager.decodeSubscription(editSubId)
|
||||
if (subItem != null) {
|
||||
@@ -73,6 +77,34 @@ class SubEditActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setupProfileRemarkInputs() {
|
||||
val suggestions = MmkvManager.decodeAllServerList()
|
||||
.mapNotNull { id -> MmkvManager.decodeServerConfig(id)?.remarks }
|
||||
.filter { it.isNotBlank() }
|
||||
.distinct()
|
||||
|
||||
setupProfileRemarkInput(binding.etPreProfile, binding.btnPreProfileDropdown, suggestions)
|
||||
setupProfileRemarkInput(binding.etNextProfile, binding.btnNextProfileDropdown, suggestions)
|
||||
}
|
||||
|
||||
private fun setupProfileRemarkInput(
|
||||
input: AutoCompleteTextView,
|
||||
dropdownButton: ImageButton,
|
||||
suggestions: List<String>
|
||||
) {
|
||||
val adapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, suggestions)
|
||||
input.setAdapter(adapter)
|
||||
input.threshold = 0
|
||||
|
||||
dropdownButton.setOnClickListener {
|
||||
input.requestFocus()
|
||||
input.showDropDown()
|
||||
}
|
||||
input.setOnClickListener {
|
||||
input.showDropDown()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.BaseAdapterListener
|
||||
import com.v2ray.ang.databinding.ActivityUserAssetBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.entities.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.appcompat.app.AlertDialog
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityUserAssetUrlBinding
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.entities.AssetUrlItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.v2ray.ang.util
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.LOOPBACK
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -108,20 +109,15 @@ object HttpUtil {
|
||||
* @param httpPort The HTTP port to use.
|
||||
* @return The content of the URL as a string.
|
||||
*/
|
||||
fun getUrlContent(
|
||||
url: String,
|
||||
timeout: Int,
|
||||
httpPort: Int = 0,
|
||||
proxyUsername: String? = null,
|
||||
proxyPassword: String? = null
|
||||
): String? {
|
||||
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = true)
|
||||
fun getUrlContent(request: UrlContentRequest): String? {
|
||||
val url = request.url ?: return null
|
||||
val client = buildOkHttpClient(request.timeout, request.httpPort, request.proxyUsername, request.proxyPassword, followRedirects = true)
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.header("Connection", "close")
|
||||
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
|
||||
if (request.httpPort != 0 && !request.proxyUsername.isNullOrBlank() && !request.proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(request.proxyUsername, request.proxyPassword))
|
||||
}
|
||||
try {
|
||||
client.newCall(requestBuilder.build()).execute().use { response ->
|
||||
@@ -147,25 +143,18 @@ object HttpUtil {
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun getUrlContentWithUserAgent(
|
||||
url: String?,
|
||||
userAgent: String?,
|
||||
timeout: Int = 15000,
|
||||
httpPort: Int = 0,
|
||||
proxyUsername: String? = null,
|
||||
proxyPassword: String? = null
|
||||
): String {
|
||||
var currentUrl = url
|
||||
fun getUrlContentWithUserAgent(request: UrlContentRequest): String {
|
||||
var currentUrl = request.url
|
||||
var redirects = 0
|
||||
val maxRedirects = 3
|
||||
|
||||
while (redirects++ < maxRedirects) {
|
||||
if (currentUrl == null) continue
|
||||
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = false)
|
||||
val finalUserAgent = if (userAgent.isNullOrBlank()) {
|
||||
val client = buildOkHttpClient(request.timeout, request.httpPort, request.proxyUsername, request.proxyPassword, followRedirects = false)
|
||||
val finalUserAgent = if (request.userAgent.isNullOrBlank()) {
|
||||
"v2rayNG/${BuildConfig.VERSION_NAME}"
|
||||
} else {
|
||||
userAgent
|
||||
request.userAgent
|
||||
}
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(currentUrl)
|
||||
@@ -175,8 +164,8 @@ object HttpUtil {
|
||||
|
||||
applyEmbeddedBasicAuthHeader(currentUrl, requestBuilder)
|
||||
|
||||
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
|
||||
if (request.httpPort != 0 && !request.proxyUsername.isNullOrBlank() && !request.proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(request.proxyUsername, request.proxyPassword))
|
||||
}
|
||||
|
||||
client.newCall(requestBuilder.build()).execute().use { response ->
|
||||
@@ -271,20 +260,17 @@ object HttpUtil {
|
||||
}
|
||||
|
||||
fun downloadToFile(
|
||||
url: String,
|
||||
targetFile: File,
|
||||
timeout: Int = 15000,
|
||||
httpPort: Int = 0,
|
||||
proxyUsername: String? = null,
|
||||
proxyPassword: String? = null
|
||||
request: UrlContentRequest,
|
||||
targetFile: File
|
||||
): Boolean {
|
||||
val client = buildOkHttpClient(timeout, httpPort, proxyUsername, proxyPassword, followRedirects = true)
|
||||
val url = request.url ?: return false
|
||||
val client = buildOkHttpClient(request.timeout, request.httpPort, request.proxyUsername, request.proxyPassword, followRedirects = true)
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.header("Connection", "close")
|
||||
if (httpPort != 0 && !proxyUsername.isNullOrBlank() && !proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword))
|
||||
if (request.httpPort != 0 && !request.proxyUsername.isNullOrBlank() && !request.proxyPassword.isNullOrBlank()) {
|
||||
requestBuilder.header("Proxy-Authorization", Credentials.basic(request.proxyUsername, request.proxyPassword))
|
||||
}
|
||||
|
||||
return try {
|
||||
|
||||
@@ -14,8 +14,8 @@ import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.GroupMapItem
|
||||
import com.v2ray.ang.dto.ServersCache
|
||||
import com.v2ray.ang.dto.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.ServersCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.dto.SubscriptionUpdateResult
|
||||
import com.v2ray.ang.dto.TestServiceMessage
|
||||
import com.v2ray.ang.extension.matchesPattern
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.v2ray.ang.dto.RulesetItem
|
||||
import com.v2ray.ang.dto.entities.RulesetItem
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.v2ray.ang.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.v2ray.ang.dto.SubscriptionCache
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.dto.entities.SubscriptionCache
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsChangeManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
|
||||
@@ -2,8 +2,9 @@ package com.v2ray.ang.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AssetUrlCache
|
||||
import com.v2ray.ang.dto.AssetUrlItem
|
||||
import com.v2ray.ang.dto.entities.AssetUrlCache
|
||||
import com.v2ray.ang.dto.entities.AssetUrlItem
|
||||
import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.extension.concatUrl
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
@@ -92,7 +93,18 @@ class UserAssetViewModel : ViewModel() {
|
||||
val targetTemp = File(extDir, item.remarks + "_temp")
|
||||
val target = File(extDir, item.remarks)
|
||||
try {
|
||||
if (HttpUtil.downloadToFile(item.url, targetTemp, 15000, httpPort, proxyUsername, proxyPassword)) {
|
||||
if (
|
||||
HttpUtil.downloadToFile(
|
||||
UrlContentRequest(
|
||||
url = item.url,
|
||||
timeout = 15000,
|
||||
httpPort = httpPort,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword
|
||||
),
|
||||
targetTemp
|
||||
)
|
||||
) {
|
||||
targetTemp.renameTo(target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -209,12 +209,33 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_pre_profile" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_pre_profile"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/sub_setting_pre_profile_tip"
|
||||
android:inputType="text" />
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/et_pre_profile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:completionThreshold="0"
|
||||
android:hint="@string/sub_setting_pre_profile_tip"
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionDone"
|
||||
android:paddingEnd="40dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_pre_profile_dropdown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/sub_setting_pre_profile"
|
||||
android:src="@drawable/ic_arrow_drop_down" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -229,12 +250,33 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/sub_setting_next_profile" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_next_profile"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/sub_setting_pre_profile_tip"
|
||||
android:inputType="text" />
|
||||
android:layout_marginTop="@dimen/padding_spacing_dp8"
|
||||
android:layout_marginBottom="@dimen/padding_spacing_dp8">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/et_next_profile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:completionThreshold="0"
|
||||
android:hint="@string/sub_setting_pre_profile_tip"
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionDone"
|
||||
android:paddingEnd="40dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_next_profile_dropdown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/sub_setting_next_profile"
|
||||
android:src="@drawable/ic_arrow_drop_down" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<string name="notification_action_stop_v2ray">واڌاشتن</string>
|
||||
<string name="toast_permission_denied">گرؽڌن موجوز مومکن نؽڌ</string>
|
||||
<string name="toast_permission_denied_notification">گرؽڌن موجوز وارسۊوی مومکن نؽڌ</string>
|
||||
<string name="notification_action_more">سی گرؽڌن دۉسمندیا بیشتر کیلیک کوݩ</string>
|
||||
<string name="notification_action_more">سی گرؽڌن دووسمندیا قلوه کیلیک کوݩ</string>
|
||||
<string name="toast_services_start">ره وستن خدمات</string>
|
||||
<string name="toast_services_stop">واڌاشتن خدمات</string>
|
||||
<string name="toast_services_success">ره وستن خدمات وا مووفقیت ٱنجوم وابی</string>
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="menu_item_import_config_clipboard">و من ٱووردن کانفیگ ز کلیپ بورد</string>
|
||||
<string name="menu_item_import_config_local">و من ٱووردن کانفیگ ز مهلی</string>
|
||||
<string name="menu_item_import_config_policy_group">ٱووردن [بونکۊ سیاست]</string>
|
||||
<string name="menu_item_import_config_proxy_chain">Add [Proxy chain]</string>
|
||||
<string name="menu_item_import_config_proxy_chain">ٱووردن [زنجیره پروکسی]</string>
|
||||
<string name="menu_item_import_config_manually_vmess">هؽل دستی[VMess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">هؽل دستی[VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">هؽل دستی[Shadowsocks]</string>
|
||||
@@ -86,8 +86,8 @@
|
||||
<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>
|
||||
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما (,) ز یک جوڌا ابۊن)</string>
|
||||
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما (,) ز یک جوڌا ابۊن)</string>
|
||||
<string name="server_lab_local_mtu">Mtu(اختیاری، پؽش فرز 1420)</string>
|
||||
<string name="server_lab_kcp_mtu">mKCP MTU (اختیاری، پؽش فرز هسته 1350)</string>
|
||||
<string name="server_lab_kcp_tti">mKCP TTI (اختیاری، پؽش فرز هسته 50)</string>
|
||||
@@ -177,16 +177,16 @@
|
||||
<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="title_pref_sniffing_enabled">ره وندن تجزیه وو تئلیل کتنا (Sniffing)</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>
|
||||
<string name="summary_pref_local_dns_enabled">درخاستا DNS و هسته و من ایان وو و دست ماژول DNS پردازشت ابۊن (پؽشنهاڌ ابۊ ٱر لنگ تور جوستن سی دور زیڌن نشۊویا LAN وو وولات ٱسلی هڌین ره ونده بۊ)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">ره وندن DNS جئلی</string>
|
||||
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من یقرد ز برنومه یل کار نکونه)</string>
|
||||
<string name="summary_pref_fake_dns_enabled">DNS مهلی نشۊویا IP جئلی ن وورگنه (زل تر، ٱما گاشڌ من ی قرده ز برنومه یل کار نکونه)</string>
|
||||
|
||||
<string name="title_pref_ipv6_enabled">ره وندن IPv6</string>
|
||||
<string name="summary_pref_ipv6_enabled">نشۊویا IPv6 وو تورا IPv6 ن ز رابت VPN ره ونین</string>
|
||||
@@ -211,21 +211,21 @@
|
||||
|
||||
<string name="title_pref_delay_test_url">نشۊوی اینترنتی آزمایش تئخیر واقعی</string>
|
||||
<string name="summary_pref_delay_test_url">نشۊوی اینترنتی</string>
|
||||
<string name="title_pref_real_ping_concurrency">هموورگی آزمایش تئخیر واقعی</string>
|
||||
<string name="title_pref_real_ping_concurrency">تعداد هوم زمووی تست تئخیر واقعی</string>
|
||||
|
||||
<string name="title_pref_ip_api_url">نشۊوی اینترنتی آزمایش دووسمندیا منپیز هیم سکویی</string>
|
||||
<string name="summary_pref_ip_api_url">نشۊوی اینترنتی</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">هشتن منپیزا ز شبکه مهلی</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا نشۊوی IP ایسا، ز ره socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل اعتماد فعال بۊ تا ز منپیز غیر موجاز جلو گری بۊ.</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا نشۊوی IP ایسا، ز ره socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل ائتماد ره ونده بۊ تا ز منپیز قیر موجاز جلو گری بۊ.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">منپیزا ز شبکه مهلی ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">اجازه نا ٱمن</string>
|
||||
<string name="summary_pref_allow_insecure">مجال و کار بوردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن فعال هڌ.</string>
|
||||
<string name="summary_pref_allow_insecure">مجال و کار بوردن TLS ب تۉر پؽش فرز، موجوز نا ٱمن ره ونده هڌ.</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
|
||||
<string name="title_pref_enable_local_proxy">ره وندن پروکسی مهلی</string>
|
||||
<string name="summary_pref_enable_local_proxy">هرسا قیرفعال بۊ، هیچ پروکسی SOCKS من دسرس نؽ، سی دل هیمو ورۊ رسۊوی یل اشتراک وو کارا جۊر یو نترن ز پروکسی استفاڌه کونن.</string>
|
||||
<string name="summary_pref_enable_local_proxy">هرسا ره نوسته بۊ، هیچ پروکسی SOCKS من دسرس نؽ، سی دل هیمو ورۊ رسۊوی یل اشتراک وو کارا جۊر یو نترن پروکسی ن و کار گرن.</string>
|
||||
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
|
||||
<string name="summary_pref_socks_enable_udp">ائراز هۊویت socks5 مجال استفاڌه ز UDP کاملن ایمن نؽ</string>
|
||||
<string name="summary_pref_socks_port">پورت پروکسی مهلی</string>
|
||||
@@ -249,9 +249,9 @@
|
||||
<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="summary_pref_double_column_display">نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینوو ترین موئتوا بیشتری ن سیل کۊنین. سی ره وستن، وا برنومه ن ز نۊ ره ونین.</string>
|
||||
|
||||
<string name="title_pref_group_all_display">نشووݩ داڌن پوی بونکۊ یل ن فعال کۊنین</string>
|
||||
<string name="title_pref_group_all_display">نشووݩ داڌن پوی بونکۊ یل ن ره ونین</string>
|
||||
<string name="summary_pref_group_all_display">ی بلگه «پوی بونکۊ یل کانفیگ» ازاف کۊنین</string>
|
||||
<!-- AboutActivity -->
|
||||
<string name="title_pref_feedback">فشناڌن منشڌ</string>
|
||||
@@ -268,18 +268,18 @@
|
||||
<string name="title_pref_promotion">تبلیقات</string>
|
||||
|
||||
<string name="title_pref_auto_update_subscription">ورۊ کردن خوتکار اشتراکا</string>
|
||||
<string name="summary_pref_auto_update_subscription">اشتراکا خوتۉ ن و تۉر خوتکار وا فاسله زمۊوی من پس زمینه ورۊ کۊنین. ای ویژیی من پوی دسگایل گاشڌ همیشه کار نکونه</string>
|
||||
<string name="summary_pref_auto_update_subscription">اشتراکا خوتووݩ ن و تۉر خوتکار وا فاسله زمۊوی من پس زمینه ورۊ کۊنین. ای ویژیی من پوی دسگایل گاشڌ همیشه کار نکونه</string>
|
||||
<string name="title_pref_auto_update_interval">فاسله ورۊ کردن خوتکار (اقلن وا 15 دؽقه بۊ)</string>
|
||||
|
||||
<string name="title_core_loglevel">سئت گوزارشا</string>
|
||||
<string name="title_outbound_domain_resolve_method">بارت پؽش هل دامنه دری</string>
|
||||
<string name="title_mode">هالت</string>
|
||||
<string name="title_mode_help">سی دووسمندیا وو هیاری بیشتر، ری ای هؽل بزݩ</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">فعال کردن ویژیی Hev TUN</string>
|
||||
<string name="summary_pref_use_hev_tunnel">مجالی ک فعال بۊ، TUN ز hev-socks5-tunnel استفاڌه اکونه؛ ٱر فعال نبۊ، زهمو xray-core استفاڌه اکونه.</string>
|
||||
<string name="title_pref_use_hev_tunnel">ره وندن ویژیی Hev TUN</string>
|
||||
<string name="summary_pref_use_hev_tunnel">مجالی ک ره ونده بۊ، TUN ایا hev-socks5-tunnel ن و کار اگره؛ ٱر ره نوۊفته بۊ، همو xray-core ن و کار اگره.</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">Hev Tun سئت گوزارشا</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">زمووݩ مندیر بیڌن خوندن/هؽل کردن Hev Tun (سانیه) (پؽش فرز tcp، udp 300،60)</string>
|
||||
|
||||
@@ -290,19 +290,19 @@
|
||||
<string name="title_del_all_config">پاک کردن پوی کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری بونکۊ سکویی</string>
|
||||
<string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر بونکۊ سکویی</string>
|
||||
<string name="title_export_all">و در کشیڌن کانفیگا غیر سفارشی بونکۊ سکویی من کلیپ بورد</string>
|
||||
<string name="title_export_all">و در کشیڌن کانفیگا قیر سفارشی بونکۊ سکویی من کلیپ بورد</string>
|
||||
<string name="title_sub_setting">سامووا بونکۊ اشتراک</string>
|
||||
<string name="sub_setting_remarks">نیشتنا</string>
|
||||
<string name="sub_setting_url">نشۊوی اینترنتی اختیاری</string>
|
||||
<string name="sub_setting_user_agent">User Agent</string>
|
||||
<string name="sub_setting_filter">نوم موستعار فیلتر</string>
|
||||
<string name="sub_setting_enable">فعال بیڌن ورۊ کردن</string>
|
||||
<string name="sub_auto_update">فعال بیڌن ورۊ کردن خوتکار</string>
|
||||
<string name="sub_setting_enable">ره وندن ورۊ کردن</string>
|
||||
<string name="sub_auto_update">ره وندن ورۊ کردن خوتکار</string>
|
||||
<string name="sub_allow_insecure_url">موجاز کردن نشۊوی HTTP نا ٱمن</string>
|
||||
<string name="sub_setting_pre_profile">نوم موستعار پروکسی دیندایی</string>
|
||||
<string name="sub_setting_next_profile">نوم موستعار پروکسی نیایی</string>
|
||||
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
|
||||
<string name="toast_invalid_update_interval">فاسله ورۊ کردن نا موئتبر هڌ. اقل مقدار 15 دؽقه هڌ.</string>
|
||||
<string name="toast_invalid_update_interval">فاسله ورۊ کردن نا موئتبر هڌ. هدقل مقدار 15 دؽقه هڌ.</string>
|
||||
<string name="title_sub_update">ورۊ کردن اشتراک بونکۊ سکویی</string>
|
||||
<string name="title_ping_all_server">Tcping کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا بونکۊ سکویی</string>
|
||||
@@ -328,17 +328,17 @@
|
||||
<!-- RoutingSettingActivity -->
|
||||
<string name="routing_settings_domain_strategy">نشقه دامنه</string>
|
||||
<string name="routing_settings_title">سامووا تور جوستن</string>
|
||||
<string name="routing_settings_tips">وا کاما ز یک جوڌا ابۊن (,)؛ یکی ن بزنین: domain، ip یا process</string>
|
||||
<string name="routing_settings_tips">وا کاما (,) ز یک جوڌا ابۊن؛ یکی ن بزنین: domain، ip یا process</string>
|
||||
<string name="routing_settings_save">زفت کردن</string>
|
||||
<string name="routing_settings_delete">روفتن</string>
|
||||
<string name="routing_settings_rule_title">سامووا قانۉݩ تور جوستن</string>
|
||||
<string name="routing_settings_add_rule">ازاف کردن قانۉݩ</string>
|
||||
<string name="routing_settings_rule_title">سامووا قانووݩ تور جوستن</string>
|
||||
<string name="routing_settings_add_rule">ازاف کردن قانووݩ</string>
|
||||
<string name="routing_settings_import_predefined_rulesets">و من ٱووردن قانووا</string>
|
||||
<string name="routing_settings_import_rulesets_tip">قانووایی ک هیم سکو هڌسۉݩ پاک ابۊن، هنی هم اخۊی پاکسۉݩ کۊنی؟</string>
|
||||
<string name="routing_settings_import_rulesets_tip">قانووایی ک هیم سکو هڌسووݩ پاک ابۊن، هنی هم اخۊی پاکسووݩ کۊنی؟</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">و من ٱووردن قانووا ز کلیپ بورد</string>
|
||||
<string name="routing_settings_import_rulesets_from_qrcode">و من ٱووردن قانووا ز QRcode</string>
|
||||
<string name="routing_settings_export_rulesets_to_clipboard">و در کشیڌن قانووا وو زفت من کلیپ بورد</string>
|
||||
<string name="routing_settings_locked">چفت هڌ، ای قانؤنن مجال و من ٱووردن ز پؽش سامووا زفت کۊنین</string>
|
||||
<string name="routing_settings_locked">چفت هڌ، ای قانووݩ ن مجال و من ٱووردن ز پؽش سامووا زفت کۊنین</string>
|
||||
<string name="routing_settings_domain" translatable="false">domain</string>
|
||||
<string name="routing_settings_ip" translatable="false">ip</string>
|
||||
<string name="routing_settings_port" translatable="false">port</string>
|
||||
@@ -377,12 +377,12 @@
|
||||
<string name="title_policy_group_type">نوع بونکۊ سیاست</string>
|
||||
<string name="title_policy_group_subscription_id">ز بونکۊ اشتراک</string>
|
||||
<string name="title_policy_group_subscription_filter">توزیهات فیلتر معمۊلی</string>
|
||||
<string name="server_proxy_chain_members">اعضای زنجیره پروکسی</string>
|
||||
<string name="server_proxy_chain_pick_members">اینجا بزنین تا عضو اِنتخاب اکه</string>
|
||||
<string name="server_proxy_chain_member_unselected">یه عضو اِنتخاب کۊنین</string>
|
||||
<string name="server_proxy_chain_members_unselected">لطفا سی هر ردیو زنجیره، remark اِنتخاب کۊنین</string>
|
||||
<string name="server_proxy_chain_members_insufficient">تعداد عضوها کافی نیس</string>
|
||||
<string name="server_proxy_chain_members_invalid">اعضای نامعتبر زنجیره: %1$s</string>
|
||||
<string name="server_proxy_chain_members">ٱئزا زنجیره پروکسی</string>
|
||||
<string name="server_proxy_chain_pick_members">سی پسند عوزو ایچو بزنین</string>
|
||||
<string name="server_proxy_chain_member_unselected">ی عوزو پسند کۊنین</string>
|
||||
<string name="server_proxy_chain_members_unselected">سی هر ردیف زنجیره remark پسند کۊنین</string>
|
||||
<string name="server_proxy_chain_members_insufficient">تعداد ٱئزا بس نؽ</string>
|
||||
<string name="server_proxy_chain_members_invalid">ٱئزا ناموئتبر زنجیره: %1$s</string>
|
||||
|
||||
<!-- BackupActivity -->
|
||||
<string name="title_configuration_backup_restore">لادراری گرؽڌن & وورگندن</string>
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.v2ray.ang.fmt
|
||||
import android.util.Base64
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.dto.ProfileItem
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
|
||||
Reference in New Issue
Block a user